mirror of
https://github.com/aljazceru/goose.git
synced 2026-01-01 21:44:26 +01:00
E2E: added provider switching and MCP testing (#2029)
This commit is contained in:
391
ui/desktop/package-lock.json
generated
391
ui/desktop/package-lock.json
generated
@@ -64,6 +64,7 @@
|
||||
"@electron/fuses": "^1.8.0",
|
||||
"@eslint/js": "^8.56.0",
|
||||
"@hey-api/openapi-ts": "^0.64.4",
|
||||
"@modelcontextprotocol/sdk": "^1.8.0",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/cors": "^2.8.17",
|
||||
@@ -2424,6 +2425,312 @@
|
||||
"node": ">= 12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz",
|
||||
"integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
"cors": "^2.8.5",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"eventsource": "^3.0.2",
|
||||
"express": "^5.0.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"pkce-challenge": "^4.1.0",
|
||||
"raw-body": "^3.0.0",
|
||||
"zod": "^3.23.8",
|
||||
"zod-to-json-schema": "^3.24.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/accepts": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "^3.0.0",
|
||||
"negotiator": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
||||
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "^3.1.2",
|
||||
"content-type": "^1.0.5",
|
||||
"debug": "^4.4.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"qs": "^6.14.0",
|
||||
"raw-body": "^3.0.0",
|
||||
"type-is": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
||||
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
||||
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/express": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.0",
|
||||
"content-disposition": "^1.0.0",
|
||||
"content-type": "^1.0.5",
|
||||
"cookie": "^0.7.1",
|
||||
"cookie-signature": "^1.2.1",
|
||||
"debug": "^4.4.0",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"etag": "^1.8.1",
|
||||
"finalhandler": "^2.1.0",
|
||||
"fresh": "^2.0.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"merge-descriptors": "^2.0.0",
|
||||
"mime-types": "^3.0.0",
|
||||
"on-finished": "^2.4.1",
|
||||
"once": "^1.4.0",
|
||||
"parseurl": "^1.3.3",
|
||||
"proxy-addr": "^2.0.7",
|
||||
"qs": "^6.14.0",
|
||||
"range-parser": "^1.2.1",
|
||||
"router": "^2.2.0",
|
||||
"send": "^1.1.0",
|
||||
"serve-static": "^2.2.0",
|
||||
"statuses": "^2.0.1",
|
||||
"type-is": "^2.0.1",
|
||||
"vary": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
|
||||
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"parseurl": "^1.3.3",
|
||||
"statuses": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/fresh": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
||||
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
|
||||
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
||||
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "^1.54.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
|
||||
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.6.3",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/send": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
|
||||
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.5",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"etag": "^1.8.1",
|
||||
"fresh": "^2.0.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"mime-types": "^3.0.1",
|
||||
"ms": "^2.1.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"range-parser": "^1.2.1",
|
||||
"statuses": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
||||
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"parseurl": "^1.3.3",
|
||||
"send": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/type-is": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
||||
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
"media-typer": "^1.1.0",
|
||||
"mime-types": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -8168,6 +8475,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eventsource": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz",
|
||||
"integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eventsource-parser": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource-parser": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
|
||||
@@ -8177,6 +8497,16 @@
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource/node_modules/eventsource-parser": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
|
||||
"integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||
@@ -8267,6 +8597,22 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/express-rate-limit": {
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
|
||||
"integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/express-rate-limit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": "^4.11 || 5 || ^5.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/express-ws": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz",
|
||||
@@ -10190,6 +10536,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-promise": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-reference": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||
@@ -13435,6 +13788,16 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/pkce-challenge": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz",
|
||||
"integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
|
||||
@@ -14856,6 +15219,33 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
"depd": "^2.0.0",
|
||||
"is-promise": "^4.0.0",
|
||||
"parseurl": "^1.3.3",
|
||||
"path-to-regexp": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/router/node_modules/path-to-regexp": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
|
||||
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
@@ -17533,7 +17923,6 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
|
||||
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"@electron/fuses": "^1.8.0",
|
||||
"@eslint/js": "^8.56.0",
|
||||
"@hey-api/openapi-ts": "^0.64.4",
|
||||
"@modelcontextprotocol/sdk": "^1.8.0",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/cors": "^2.8.17",
|
||||
|
||||
@@ -1,20 +1,131 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
import { _electron as electron } from '@playwright/test';
|
||||
import { join } from 'path';
|
||||
import { spawn, exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { showTestName, clearTestName } from './test-overlay';
|
||||
|
||||
const { runningQuotes } = require('./basic-mcp');
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
// Define provider interface
|
||||
type Provider = {
|
||||
name: string;
|
||||
testPath: string;
|
||||
};
|
||||
|
||||
// Create test fixture type
|
||||
type TestFixtures = {
|
||||
provider: Provider;
|
||||
};
|
||||
|
||||
// Define available providers
|
||||
const providers: Provider[] = [
|
||||
{ name: 'Databricks', testPath: 'div:has(h3:text("Databricks"))[class*="relative bg-bgApp rounded-lg"]' },
|
||||
{ name: 'Google', testPath: 'div:has(h3:text("Google"))[class*="relative bg-bgApp rounded-lg"]' }
|
||||
];
|
||||
|
||||
// Create test with fixtures
|
||||
const test = base.extend<TestFixtures>({
|
||||
provider: [providers[0], { option: true }], // Default to first provider (Databricks)
|
||||
});
|
||||
|
||||
// Store mainWindow reference
|
||||
let mainWindow;
|
||||
|
||||
// Add hooks for test name overlay
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
test.beforeEach(async ({ }, testInfo) => {
|
||||
if (mainWindow) {
|
||||
// Get a clean test name without the full hierarchy
|
||||
const testName = testInfo.titlePath[testInfo.titlePath.length - 1];
|
||||
|
||||
// Get provider name if we're in a provider suite
|
||||
const providerSuite = testInfo.titlePath.find(t => t.startsWith('Provider:'));
|
||||
const providerName = providerSuite ? providerSuite.split(': ')[1] : undefined;
|
||||
|
||||
console.log(`Setting overlay for test: "${testName}"${providerName ? ` (Provider: ${providerName})` : ''}`);
|
||||
await showTestName(mainWindow, testName, providerName);
|
||||
}
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
if (mainWindow) {
|
||||
await clearTestName(mainWindow);
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to select a provider
|
||||
async function selectProvider(mainWindow: any, provider: Provider) {
|
||||
console.log(`Selecting provider: ${provider.name}`);
|
||||
|
||||
// Click the menu button (3 dots) if we're in chat
|
||||
try {
|
||||
// Wait for header and menu button to be visible
|
||||
await mainWindow.waitForSelector('div[class*="bg-bgSubtle border-b border-borderSubtle"]', { timeout: 5000 });
|
||||
await mainWindow.waitForTimeout(1000); // Give UI time to stabilize
|
||||
|
||||
const menuButton = await mainWindow.waitForSelector('button:has(svg)', {
|
||||
timeout: 5000,
|
||||
state: 'visible'
|
||||
});
|
||||
await menuButton.click();
|
||||
|
||||
// Wait for menu to be visible
|
||||
await mainWindow.waitForTimeout(1000);
|
||||
|
||||
// Click "Reset provider and model"
|
||||
const resetProviderButton = await mainWindow.waitForSelector('button:has-text("Reset provider and model")', { timeout: 5000 });
|
||||
await resetProviderButton.click();
|
||||
|
||||
// Wait for page to start refreshing
|
||||
await mainWindow.waitForTimeout(1000);
|
||||
|
||||
// Wait for page to finish reloading
|
||||
await mainWindow.reload();
|
||||
await mainWindow.waitForLoadState('networkidle');
|
||||
|
||||
} catch (e) {
|
||||
console.log('Already on provider selection screen or error:', e);
|
||||
}
|
||||
|
||||
// Wait for provider selection screen
|
||||
const heading = await mainWindow.waitForSelector('h2:has-text("Choose a Provider")', { timeout: 10000 });
|
||||
const headingText = await heading.textContent();
|
||||
expect(headingText).toBe('Choose a Provider');
|
||||
|
||||
// Find and verify the provider card container
|
||||
console.log(`Looking for ${provider.name} card...`);
|
||||
const providerContainer = await mainWindow.waitForSelector(provider.testPath);
|
||||
expect(await providerContainer.isVisible()).toBe(true);
|
||||
|
||||
// Find the Launch button within the provider container
|
||||
console.log(`Looking for Launch button in ${provider.name} card...`);
|
||||
const launchButton = await providerContainer.waitForSelector('button:has-text("Launch")');
|
||||
expect(await launchButton.isVisible()).toBe(true);
|
||||
|
||||
// Take screenshot before clicking
|
||||
await mainWindow.screenshot({ path: `test-results/before-${provider.name.toLowerCase()}-click.png` });
|
||||
|
||||
// Click the Launch button
|
||||
await launchButton.click();
|
||||
|
||||
// Wait for chat interface to appear
|
||||
const chatTextarea = await mainWindow.waitForSelector('textarea[placeholder*="What can goose help with?"]',
|
||||
{ timeout: 15000 });
|
||||
expect(await chatTextarea.isVisible()).toBe(true);
|
||||
|
||||
// Take screenshot of chat interface
|
||||
await mainWindow.screenshot({ path: `test-results/chat-interface-${provider.name.toLowerCase()}.png` });
|
||||
}
|
||||
|
||||
test.describe('Goose App', () => {
|
||||
let electronApp;
|
||||
let appProcess;
|
||||
let mainWindow;
|
||||
let isProviderSelected = false;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
console.log('Starting Electron app...');
|
||||
|
||||
|
||||
// Start the electron-forge process
|
||||
appProcess = spawn('npm', ['run', 'start-gui'], {
|
||||
cwd: join(__dirname, '../..'),
|
||||
@@ -48,28 +159,21 @@ test.describe('Goose App', () => {
|
||||
...process.env,
|
||||
ELECTRON_IS_DEV: '1',
|
||||
NODE_ENV: 'development'
|
||||
},
|
||||
recordVideo: {
|
||||
dir: 'test-results/videos/',
|
||||
size: { width: 620, height: 680 }
|
||||
}
|
||||
});
|
||||
|
||||
// Get the main window once for all tests
|
||||
mainWindow = await electronApp.firstWindow();
|
||||
await mainWindow.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Check if we're already on the chat screen
|
||||
try {
|
||||
const chatInput = await mainWindow.waitForSelector('textarea[placeholder*="What can goose help with?"]',
|
||||
{ timeout: 5000 });
|
||||
isProviderSelected = await chatInput.isVisible();
|
||||
console.log('Provider already selected, chat interface visible');
|
||||
} catch (e) {
|
||||
console.log('On provider selection screen');
|
||||
isProviderSelected = false;
|
||||
}
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
console.log('Final cleanup...');
|
||||
|
||||
|
||||
// Close the test instance
|
||||
if (electronApp) {
|
||||
await electronApp.close().catch(console.error);
|
||||
@@ -113,141 +217,312 @@ test.describe('Goose App', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('verify initial screen and select provider if needed', async () => {
|
||||
console.log('Checking initial screen state...');
|
||||
|
||||
if (!isProviderSelected) {
|
||||
// Take screenshot of provider selection screen
|
||||
await mainWindow.screenshot({ path: 'test-results/provider-selection.png' });
|
||||
test.describe('General UI', () => {
|
||||
test('dark mode toggle', async () => {
|
||||
console.log('Testing dark mode toggle...');
|
||||
|
||||
// Click the three dots menu button in the top right
|
||||
await mainWindow.waitForSelector('div[class*="bg-bgSubtle border-b border-borderSubtle"]');
|
||||
const menuButton = await mainWindow.waitForSelector('button:has(svg)', { timeout: 10000 });
|
||||
await menuButton.click();
|
||||
|
||||
// Find and click the dark mode toggle button
|
||||
const darkModeButton = await mainWindow.waitForSelector('button:has-text("Dark Mode"), button:has-text("Light Mode")');
|
||||
|
||||
// Get initial state
|
||||
const isDarkMode = await mainWindow.evaluate(() => document.documentElement.classList.contains('dark'));
|
||||
console.log('Initial dark mode state:', isDarkMode);
|
||||
|
||||
// Click to toggle
|
||||
await darkModeButton.click();
|
||||
|
||||
// Verify the change
|
||||
const newDarkMode = await mainWindow.evaluate(() => document.documentElement.classList.contains('dark'));
|
||||
expect(newDarkMode).toBe(!isDarkMode);
|
||||
|
||||
// Take screenshot to verify and pause to show the change
|
||||
await mainWindow.screenshot({ path: 'test-results/dark-mode-toggle.png' });
|
||||
await mainWindow.waitForTimeout(2000); // Pause in dark/light mode
|
||||
|
||||
// Toggle back to original state
|
||||
await darkModeButton.click();
|
||||
|
||||
// Verify provider selection screen
|
||||
const heading = await mainWindow.waitForSelector('h2:has-text("Choose a Provider")', { timeout: 10000 });
|
||||
const headingText = await heading.textContent();
|
||||
expect(headingText).toBe('Choose a Provider');
|
||||
|
||||
// Find and verify the Databricks card container
|
||||
console.log('Looking for Databricks card...');
|
||||
const databricksContainer = await mainWindow.waitForSelector(
|
||||
'div:has(h3:text("Databricks"))[class*="relative bg-bgApp rounded-lg"]'
|
||||
);
|
||||
expect(await databricksContainer.isVisible()).toBe(true);
|
||||
|
||||
// Find the Launch button within the Databricks container
|
||||
console.log('Looking for Launch button in Databricks card...');
|
||||
const launchButton = await databricksContainer.waitForSelector('button:has-text("Launch")');
|
||||
expect(await launchButton.isVisible()).toBe(true);
|
||||
|
||||
// Take screenshot before clicking
|
||||
await mainWindow.screenshot({ path: 'test-results/before-databricks-click.png' });
|
||||
|
||||
// Click the Launch button
|
||||
await launchButton.click();
|
||||
|
||||
// Wait for chat interface to appear
|
||||
const chatTextarea = await mainWindow.waitForSelector('textarea[placeholder*="What can goose help with?"]',
|
||||
{ timeout: 15000 });
|
||||
expect(await chatTextarea.isVisible()).toBe(true);
|
||||
} else {
|
||||
console.log('Provider already selected, skipping provider selection test');
|
||||
}
|
||||
|
||||
// Take screenshot of current state
|
||||
await mainWindow.screenshot({ path: 'test-results/chat-interface.png' });
|
||||
// Pause to show return to original state
|
||||
await mainWindow.waitForTimeout(2000);
|
||||
|
||||
// Close menu with ESC key
|
||||
await mainWindow.keyboard.press('Escape');
|
||||
});
|
||||
});
|
||||
|
||||
test('chat interaction', async () => {
|
||||
console.log('Testing chat interaction...');
|
||||
|
||||
// Find the chat input
|
||||
const chatInput = await mainWindow.waitForSelector('textarea[placeholder*="What can goose help with?"]');
|
||||
expect(await chatInput.isVisible()).toBe(true);
|
||||
|
||||
// Type a message
|
||||
await chatInput.fill('Hello, can you help me with a simple task?');
|
||||
|
||||
// Take screenshot before sending
|
||||
await mainWindow.screenshot({ path: 'test-results/before-send.png' });
|
||||
|
||||
// Get initial message count
|
||||
const initialMessages = await mainWindow.locator('.prose').count();
|
||||
|
||||
// Send message
|
||||
await chatInput.press('Enter');
|
||||
|
||||
// Wait for loading indicator to appear (using the specific class and text)
|
||||
console.log('Waiting for loading indicator...');
|
||||
const loadingGoose = await mainWindow.waitForSelector('.text-textStandard >> text="goose is working on it…"',
|
||||
{ timeout: 10000 });
|
||||
expect(await loadingGoose.isVisible()).toBe(true);
|
||||
|
||||
// Take screenshot of loading state
|
||||
await mainWindow.screenshot({ path: 'test-results/loading-state.png' });
|
||||
|
||||
// Wait for loading indicator to disappear
|
||||
console.log('Waiting for response...');
|
||||
await mainWindow.waitForSelector('.text-textStandard >> text="goose is working on it…"',
|
||||
{ state: 'hidden', timeout: 30000 });
|
||||
|
||||
// Wait for new message to appear
|
||||
await mainWindow.waitForFunction((count) => {
|
||||
const messages = document.querySelectorAll('.prose');
|
||||
return messages.length > count;
|
||||
}, initialMessages, { timeout: 30000 });
|
||||
|
||||
// Get the latest response
|
||||
const response = await mainWindow.locator('.prose').last();
|
||||
expect(await response.isVisible()).toBe(true);
|
||||
|
||||
// Verify response has content
|
||||
const responseText = await response.textContent();
|
||||
expect(responseText).toBeTruthy();
|
||||
expect(responseText.length).toBeGreaterThan(0);
|
||||
|
||||
// Take screenshot of response
|
||||
await mainWindow.screenshot({ path: 'test-results/chat-response.png' });
|
||||
});
|
||||
for (const provider of providers) {
|
||||
test.describe(`Provider: ${provider.name}`, () => {
|
||||
test.beforeAll(async () => {
|
||||
// Select the provider once before all tests for this provider
|
||||
await selectProvider(mainWindow, provider);
|
||||
});
|
||||
|
||||
test('verify chat history', async () => {
|
||||
console.log('Testing chat history...');
|
||||
test.describe('Chat', () => {
|
||||
test('chat interaction', async () => {
|
||||
console.log(`Testing chat interaction with ${provider.name}...`);
|
||||
|
||||
// Find the chat input again
|
||||
const chatInput = await mainWindow.waitForSelector('textarea[placeholder*="What can goose help with?"]');
|
||||
// Find the chat input
|
||||
const chatInput = await mainWindow.waitForSelector('textarea[placeholder*="What can goose help with?"]');
|
||||
expect(await chatInput.isVisible()).toBe(true);
|
||||
|
||||
// Test message sending with a specific question
|
||||
await chatInput.fill('What is 2+2?');
|
||||
// Type a message
|
||||
await chatInput.fill('Hello, can you help me with a simple task?');
|
||||
|
||||
// Get initial message count
|
||||
const initialMessages = await mainWindow.locator('.prose').count();
|
||||
// Take screenshot before sending
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-before-send.png` });
|
||||
|
||||
// Send message
|
||||
await chatInput.press('Enter');
|
||||
// Get initial message count
|
||||
const initialMessages = await mainWindow.locator('.prose').count();
|
||||
|
||||
// Wait for loading indicator and response using the correct selector
|
||||
await mainWindow.waitForSelector('.text-textStandard >> text="goose is working on it…"', { timeout: 10000 });
|
||||
await mainWindow.waitForSelector('.text-textStandard >> text="goose is working on it…"',
|
||||
{ state: 'hidden', timeout: 30000 });
|
||||
// Send message
|
||||
await chatInput.press('Enter');
|
||||
|
||||
// Wait for new message
|
||||
await mainWindow.waitForFunction((count) => {
|
||||
const messages = document.querySelectorAll('.prose');
|
||||
return messages.length > count;
|
||||
}, initialMessages, { timeout: 30000 });
|
||||
// Wait for loading indicator to appear
|
||||
console.log('Waiting for loading indicator...');
|
||||
const loadingGoose = await mainWindow.waitForSelector('.text-textStandard >> text="goose is working on it…"',
|
||||
{ timeout: 10000 });
|
||||
expect(await loadingGoose.isVisible()).toBe(true);
|
||||
|
||||
// Get the latest response
|
||||
const response = await mainWindow.locator('.prose').last();
|
||||
const responseText = await response.textContent();
|
||||
expect(responseText).toBeTruthy();
|
||||
// Take screenshot of loading state
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-loading-state.png` });
|
||||
|
||||
// Check for message history
|
||||
const messages = await mainWindow.locator('.prose').all();
|
||||
expect(messages.length).toBeGreaterThanOrEqual(2);
|
||||
// Wait for loading indicator to disappear
|
||||
console.log('Waiting for response...');
|
||||
await mainWindow.waitForSelector('.text-textStandard >> text="goose is working on it…"',
|
||||
{ state: 'hidden', timeout: 30000 });
|
||||
|
||||
// Take screenshot of chat history
|
||||
await mainWindow.screenshot({ path: 'test-results/chat-history.png' });
|
||||
// Wait for new message to appear
|
||||
await mainWindow.waitForFunction((count) => {
|
||||
const messages = document.querySelectorAll('.prose');
|
||||
return messages.length > count;
|
||||
}, initialMessages, { timeout: 30000 });
|
||||
|
||||
// Test command history (up arrow)
|
||||
await chatInput.press('Control+ArrowUp');
|
||||
const inputValue = await chatInput.inputValue();
|
||||
expect(inputValue).toBe('What is 2+2?');
|
||||
});
|
||||
});
|
||||
// Get the latest response
|
||||
const response = await mainWindow.locator('.prose').last();
|
||||
expect(await response.isVisible()).toBe(true);
|
||||
|
||||
// Verify response has content
|
||||
const responseText = await response.textContent();
|
||||
expect(responseText).toBeTruthy();
|
||||
expect(responseText.length).toBeGreaterThan(0);
|
||||
|
||||
// Take screenshot of response
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-chat-response.png` });
|
||||
});
|
||||
|
||||
test('verify chat history', async () => {
|
||||
console.log(`Testing chat history with ${provider.name}...`);
|
||||
|
||||
// Find the chat input again
|
||||
const chatInput = await mainWindow.waitForSelector('textarea[placeholder*="What can goose help with?"]');
|
||||
|
||||
// Test message sending with a specific question
|
||||
await chatInput.fill('What is 2+2?');
|
||||
|
||||
// Get initial message count
|
||||
const initialMessages = await mainWindow.locator('.prose').count();
|
||||
|
||||
// Send message
|
||||
await chatInput.press('Enter');
|
||||
|
||||
// Wait for loading indicator and response
|
||||
await mainWindow.waitForSelector('.text-textStandard >> text="goose is working on it…"', { timeout: 10000 });
|
||||
await mainWindow.waitForSelector('.text-textStandard >> text="goose is working on it…"',
|
||||
{ state: 'hidden', timeout: 30000 });
|
||||
|
||||
// Wait for new message
|
||||
await mainWindow.waitForFunction((count) => {
|
||||
const messages = document.querySelectorAll('.prose');
|
||||
return messages.length > count;
|
||||
}, initialMessages, { timeout: 30000 });
|
||||
|
||||
// Get the latest response
|
||||
const response = await mainWindow.locator('.prose').last();
|
||||
const responseText = await response.textContent();
|
||||
expect(responseText).toBeTruthy();
|
||||
|
||||
// Check for message history
|
||||
const messages = await mainWindow.locator('.prose').all();
|
||||
expect(messages.length).toBeGreaterThanOrEqual(2);
|
||||
|
||||
// Take screenshot of chat history
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-chat-history.png` });
|
||||
|
||||
// Test command history (up arrow)
|
||||
await chatInput.press('Control+ArrowUp');
|
||||
const inputValue = await chatInput.inputValue();
|
||||
expect(inputValue).toBe('What is 2+2?');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('MCP Integration', () => {
|
||||
test('running quotes MCP server integration', async () => {
|
||||
console.log(`Testing Running Quotes MCP server integration with ${provider.name}...`);
|
||||
|
||||
// Clean up any existing running-quotes extensions from localStorage
|
||||
await mainWindow.evaluate(() => {
|
||||
const USER_SETTINGS_KEY = 'user_settings';
|
||||
const settings = JSON.parse(localStorage.getItem(USER_SETTINGS_KEY) || '{"extensions":[]}');
|
||||
|
||||
// Remove any running-quotes extensions
|
||||
settings.extensions = settings.extensions.filter(ext => ext.id !== 'running-quotes');
|
||||
|
||||
// Save back to localStorage
|
||||
localStorage.setItem(USER_SETTINGS_KEY, JSON.stringify(settings));
|
||||
|
||||
// Log the cleanup
|
||||
console.log('Cleaned up existing running-quotes extensions');
|
||||
});
|
||||
|
||||
// Reload the page to ensure settings are fresh
|
||||
await mainWindow.reload();
|
||||
await mainWindow.waitForLoadState('networkidle');
|
||||
|
||||
// Click the menu button (3 dots)
|
||||
const menuButton = await mainWindow.waitForSelector('button:has(svg)', { timeout: 10000 });
|
||||
await menuButton.click();
|
||||
|
||||
// Click Advanced Settings
|
||||
const advancedSettingsButton = await mainWindow.waitForSelector('button:has-text("Advanced Settings")');
|
||||
await advancedSettingsButton.click();
|
||||
|
||||
// Wait for settings page and take screenshot
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-mcp-settings-page.png` });
|
||||
|
||||
// Click Add Custom Extension button and wait for modal
|
||||
const addExtensionButton = await mainWindow.waitForSelector('button:has-text("Add Custom Extension")');
|
||||
await addExtensionButton.click();
|
||||
|
||||
// Wait for modal and form to be fully rendered
|
||||
await mainWindow.waitForSelector('form', { state: 'visible', timeout: 10000 });
|
||||
console.log('Form found, waiting for modal animation...');
|
||||
await mainWindow.waitForTimeout(1000); // Wait for modal animation
|
||||
|
||||
try {
|
||||
// Fill ID (find by label text)
|
||||
console.log('Filling ID field...');
|
||||
await mainWindow.locator('label:has-text("ID *") + input[type="text"]').fill('running-quotes');
|
||||
|
||||
// Fill Name (find by label text)
|
||||
console.log('Filling Name field...');
|
||||
await mainWindow.locator('label:has-text("Name *") + input[type="text"]').fill('Running Quotes');
|
||||
|
||||
// Fill Description (find by label text)
|
||||
console.log('Filling Description field...');
|
||||
await mainWindow.locator('label:has-text("Description *") + input[type="text"]').fill('Inspirational running quotes MCP server');
|
||||
|
||||
// Fill Command (find by label text and placeholder)
|
||||
console.log('Filling Command field...');
|
||||
const mcpScriptPath = join(__dirname, 'basic-mcp.ts');
|
||||
await mainWindow.locator('label:has-text("Command *") + input[placeholder="e.g. goosed mcp example"]')
|
||||
.fill(`node ${mcpScriptPath}`);
|
||||
|
||||
// Take screenshot of filled form
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-mcp-form-filled.png` });
|
||||
|
||||
// Add a delay to inspect the form
|
||||
console.log('Waiting 5 seconds to inspect form...');
|
||||
await mainWindow.waitForTimeout(5000);
|
||||
|
||||
// Click Add button (it's a submit button)
|
||||
console.log('Clicking Add button...');
|
||||
await mainWindow.locator('button[type="submit"]').click();
|
||||
|
||||
// Wait for success toast and take screenshot
|
||||
await mainWindow.waitForSelector('.Toastify__toast-body div div:has-text("Successfully enabled extension")',
|
||||
{ state: 'visible', timeout: 10000 });
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-mcp-extension-added.png` });
|
||||
console.log('Extension added successfully');
|
||||
|
||||
// Click Exit button to return to chat
|
||||
const exitButton = await mainWindow.waitForSelector('button:has-text("Exit")', { timeout: 5000 });
|
||||
await exitButton.click();
|
||||
|
||||
} catch (error) {
|
||||
// Take error screenshot
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-mcp-form-error.png` });
|
||||
console.error('Error during form filling:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
test('test running quotes functionality', async () => {
|
||||
console.log(`Testing running quotes functionality with ${provider.name}...`);
|
||||
|
||||
// Find the chat input
|
||||
const chatInput = await mainWindow.waitForSelector('textarea[placeholder*="What can goose help with?"]');
|
||||
expect(await chatInput.isVisible()).toBe(true);
|
||||
|
||||
// Type a message requesting a running quote
|
||||
await chatInput.fill('Can you give me an inspirational running quote using the runningQuote tool?');
|
||||
|
||||
// Take screenshot before sending
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-before-quote-request.png` });
|
||||
|
||||
// Get initial message count
|
||||
const initialMessages = await mainWindow.locator('.prose').count();
|
||||
|
||||
// Send message
|
||||
await chatInput.press('Enter');
|
||||
|
||||
// Wait for loading indicator
|
||||
const loadingIndicator = await mainWindow.waitForSelector('.text-textStandard >> text="goose is working on it…"',
|
||||
{ timeout: 10000 });
|
||||
expect(await loadingIndicator.isVisible()).toBe(true);
|
||||
|
||||
// Take screenshot of loading state
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-quote-loading.png` });
|
||||
|
||||
// Wait for loading indicator to disappear
|
||||
await mainWindow.waitForSelector('.text-textStandard >> text="goose is working on it…"',
|
||||
{ state: 'hidden', timeout: 30000 });
|
||||
|
||||
// Wait for new message to appear
|
||||
await mainWindow.waitForFunction((count) => {
|
||||
const messages = document.querySelectorAll('.prose');
|
||||
return messages.length > count;
|
||||
}, initialMessages, { timeout: 30000 });
|
||||
|
||||
// Get the latest response
|
||||
const response = await mainWindow.locator('.prose').last();
|
||||
expect(await response.isVisible()).toBe(true);
|
||||
|
||||
// Click the Output dropdown to reveal the actual quote
|
||||
const outputButton = await mainWindow.waitForSelector('button:has-text("Output")', { timeout: 5000 });
|
||||
await outputButton.click();
|
||||
|
||||
// Wait a bit and dump HTML to see structure
|
||||
await mainWindow.waitForTimeout(1000);
|
||||
const html = await mainWindow.evaluate(() => document.documentElement.outerHTML);
|
||||
console.log('Full page HTML after clicking Output:', html);
|
||||
|
||||
// Also dump just the response area HTML
|
||||
const responseHtml = await response.evaluate(el => el.outerHTML);
|
||||
console.log('Response area HTML:', responseHtml);
|
||||
|
||||
// Take screenshot before trying to find content
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-quote-response-debug.png` });
|
||||
|
||||
// Now try to get the output content
|
||||
const outputContent = await mainWindow.waitForSelector('.whitespace-pre-wrap', { timeout: 5000 });
|
||||
const outputText = await outputContent.textContent();
|
||||
console.log('Output text:', outputText);
|
||||
|
||||
// Take screenshot of expanded response
|
||||
await mainWindow.screenshot({ path: `test-results/${provider.name.toLowerCase()}-quote-response.png` });
|
||||
|
||||
// Check if the output contains one of our known quotes
|
||||
const containsKnownQuote = runningQuotes.some(({ quote, author }) =>
|
||||
outputText.includes(`"${quote}" - ${author}`)
|
||||
);
|
||||
expect(containsKnownQuote).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
71
ui/desktop/tests/e2e/basic-mcp.ts
Normal file
71
ui/desktop/tests/e2e/basic-mcp.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
|
||||
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
||||
|
||||
// Collection of running-related inspirational quotes
|
||||
const runningQuotes = [
|
||||
{
|
||||
quote: "The miracle isn't that I finished. The miracle is that I had the courage to start.",
|
||||
author: "John Bingham"
|
||||
},
|
||||
{
|
||||
quote: "Running is the greatest metaphor for life, because you get out of it what you put into it.",
|
||||
author: "Oprah Winfrey"
|
||||
},
|
||||
{
|
||||
quote: "Pain is temporary. Quitting lasts forever.",
|
||||
author: "Lance Armstrong"
|
||||
},
|
||||
{
|
||||
quote: "Run when you can, walk if you have to, crawl if you must; just never give up.",
|
||||
author: "Dean Karnazes"
|
||||
},
|
||||
{
|
||||
quote: "The only bad workout is the one that didn't happen.",
|
||||
author: "Unknown"
|
||||
},
|
||||
{
|
||||
quote: "Whether you think you can or think you can't, you're right.",
|
||||
author: "Henry Ford"
|
||||
},
|
||||
{
|
||||
quote: "If you want to run, run a mile. If you want to experience a different life, run a marathon.",
|
||||
author: "Emil Zatopek"
|
||||
},
|
||||
{
|
||||
quote: "The voice inside your head that says you can't do this is a liar.",
|
||||
author: "Unknown"
|
||||
}
|
||||
];
|
||||
|
||||
async function startServer() {
|
||||
const server = new McpServer({
|
||||
name: "Running Quotes",
|
||||
version: "1.0.0"
|
||||
});
|
||||
|
||||
server.tool("runningQuote",
|
||||
"Generates an inspirational running quote",
|
||||
async () => {
|
||||
const randomQuote = runningQuotes[Math.floor(Math.random() * runningQuotes.length)];
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `"${randomQuote.quote}" - ${randomQuote.author}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Start receiving messages on stdin and sending messages on stdout
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
}
|
||||
|
||||
// Only start the server if this is the main module
|
||||
if (require.main === module) {
|
||||
startServer().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runningQuotes
|
||||
};
|
||||
50
ui/desktop/tests/e2e/test-overlay.ts
Normal file
50
ui/desktop/tests/e2e/test-overlay.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// Helper function to show test name overlay
|
||||
async function showTestName(mainWindow: any, testName: string, providerName?: string) {
|
||||
await mainWindow.evaluate(({ name, provider }: { name: string, provider?: string }) => {
|
||||
// Remove any existing overlay
|
||||
const existing = document.getElementById('test-overlay');
|
||||
if (existing) existing.remove();
|
||||
|
||||
// Create new overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'test-overlay';
|
||||
overlay.style.cssText = `
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
z-index: 2147483647; // maximum z-index integer value
|
||||
pointer-events: none;
|
||||
text-align: center;
|
||||
max-width: 80%;
|
||||
white-space: pre-wrap;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
`;
|
||||
|
||||
const testText = `Running: ${name}`;
|
||||
const providerText = provider ? `\nProvider: ${provider}` : '';
|
||||
overlay.textContent = testText + providerText;
|
||||
|
||||
// Insert at the beginning of <html> to ensure it's above everything
|
||||
document.documentElement.insertBefore(overlay, document.documentElement.firstChild);
|
||||
|
||||
// Force a repaint to ensure the overlay is visible
|
||||
overlay.getBoundingClientRect();
|
||||
}, { name: testName, provider: providerName });
|
||||
}
|
||||
|
||||
// Helper function to clear test name overlay
|
||||
async function clearTestName(mainWindow: any) {
|
||||
await mainWindow.evaluate(() => {
|
||||
const overlay = document.getElementById('test-overlay');
|
||||
if (overlay) overlay.remove();
|
||||
});
|
||||
}
|
||||
|
||||
export { showTestName, clearTestName };
|
||||
Reference in New Issue
Block a user