From 5e9bf61006635c57c57a8c8cecf51ba1a76966ee Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 20 Jan 2025 23:23:24 -0800 Subject: [PATCH 001/138] Playwright tests framework up and running simple tests. --- ts/.gitignore | 6 ++ .../shell/.github/workflows/playwright.yml | 30 ++++++ ts/packages/shell/.gitignore | 6 ++ ts/packages/shell/package.json | 2 + ts/packages/shell/playwright.config.ts | 84 ++++++++++++++++ ts/packages/shell/test/example.spec.ts | 95 +++++++++++++++++++ ts/pnpm-lock.yaml | 41 ++++++++ 7 files changed, 264 insertions(+) create mode 100644 ts/packages/shell/.github/workflows/playwright.yml create mode 100644 ts/packages/shell/playwright.config.ts create mode 100644 ts/packages/shell/test/example.spec.ts diff --git a/ts/.gitignore b/ts/.gitignore index eaf14b38e..672e6d04c 100644 --- a/ts/.gitignore +++ b/ts/.gitignore @@ -3,3 +3,9 @@ .cert/ prod/ coverage/ + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/ts/packages/shell/.github/workflows/playwright.yml b/ts/packages/shell/.github/workflows/playwright.yml new file mode 100644 index 000000000..07f2ae15f --- /dev/null +++ b/ts/packages/shell/.github/workflows/playwright.yml @@ -0,0 +1,30 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +name: Playwright Tests +on: + push: + branches: [ dev/robgruen/electron_tests ] + pull_request: + branches: [ dev/robgruen/electron_tests ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm install -g pnpm && pnpm install + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Run Playwright tests + run: pnpm exec playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/ts/packages/shell/.gitignore b/ts/packages/shell/.gitignore index e7c3088d2..af3452bd1 100644 --- a/ts/packages/shell/.gitignore +++ b/ts/packages/shell/.gitignore @@ -2,3 +2,9 @@ node_modules dist out *.log* + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/ts/packages/shell/package.json b/ts/packages/shell/package.json index ba1ef3536..3a9dd736e 100644 --- a/ts/packages/shell/package.json +++ b/ts/packages/shell/package.json @@ -24,6 +24,7 @@ "prettier": "prettier --check . --ignore-path ../../.prettierignore", "prettier:fix": "prettier --write . --ignore-path ../../.prettierignore", "start": "electron-vite preview", + "test": "npx playwright test", "typecheck": "concurrently npm:typecheck:node npm:typecheck:web", "typecheck:node": "tsc -p tsconfig.node.json", "typecheck:web": "tsc -p tsconfig.web.json" @@ -50,6 +51,7 @@ }, "devDependencies": { "@electron-toolkit/tsconfig": "^1.0.1", + "@playwright/test": "^1.49.1", "@types/debug": "^4.1.10", "@types/dompurify": "^3.0.5", "@types/node": "^18.17.5", diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts new file mode 100644 index 000000000..0b5c698d5 --- /dev/null +++ b/ts/packages/shell/playwright.config.ts @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './test', + /* Run tests sequentially otherwise the client will complain about locked session file */ + fullyParallel: false, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + timeout: 120_000, // Set global timeout to 120 seconds + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/ts/packages/shell/test/example.spec.ts b/ts/packages/shell/test/example.spec.ts new file mode 100644 index 000000000..eb581a1ee --- /dev/null +++ b/ts/packages/shell/test/example.spec.ts @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import test, { _electron as electron, expect } from '@playwright/test'; +import { defineConfig } from '@playwright/test'; +import fs from 'node:fs'; +import path from 'node:path'; + +export default defineConfig({ + timeout: 120_000, // Set global timeout to 120 seconds +}); + +/** + * Gets the correct path based on test context (cmdline vs. VSCode extension) + * @returns The root path to the project containing the playwright configuration + */ +function getAppPath(): string { + if (fs.existsSync('playwright.config.ts')) { + return '.'; + } else { + return path.join('.', 'packages/shell'); + } +} + +test('launch app', async () => { + const electronApp = await electron.launch({ args: [getAppPath()] }) + // close app + await electronApp.close() +}); + +test('get isPackaged', async () => { + const electronApp = await electron.launch({ args: [getAppPath()] }) + const isPackaged = await electronApp.evaluate(async ({ app }) => { + // This runs in Electron's main process, parameter here is always + // the result of the require('electron') in the main app script. + return app.isPackaged + }) + console.log(isPackaged) // false (because we're in development mode) + // close app + await electronApp.close() +}); + + +test('launch app2', async() => { + // Launch Electron app. + const electronApp = await electron.launch({ args: [getAppPath()] }); + + // Evaluation expression in the Electron context. + const appPath = await electronApp.evaluate(async ({ app }) => { + // This runs in the main Electron process, parameter here is always + // the result of the require('electron') in the main app script. + return app.getAppPath(); + }); + console.log(appPath); + + // Get the first window that the app opens, wait if necessary. + const window = await electronApp.firstWindow(); + // Print the title. + console.log(await window.title()); + // Capture a screenshot. + await window.screenshot({ path: 'test-results/intro.png' }); + // Direct Electron console to Node terminal. + window.on('console', console.log); + //// Click button. + //await window.click('text=Click me'); + // Exit app. + await electronApp.close(); +}); + +test('save screenshot', async () => { + const electronApp = await electron.launch({ args: [getAppPath()] }) + const window = await electronApp.firstWindow() + await window.screenshot({ path: 'test-results/intro.png' }) + // close app + await electronApp.close() +}); + +test('example test', async () => { + const electronApp = await electron.launch({ args: [getAppPath()] }) + const isPackaged = await electronApp.evaluate(async ({ app }) => { + // This runs in Electron's main process, parameter here is always + // the result of the require('electron') in the main app script. + return app.isPackaged + }) + + expect(isPackaged).toBe(false) + + // Wait for the first BrowserWindow to open + // and return its Page object + const window = await electronApp.firstWindow() + await window.screenshot({ path: 'test-results/intro.png' }) + + // close app + await electronApp.close() +}); diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index e60aeb3c1..11100330b 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@fluidframework/build-tools': specifier: ^0.35.0-247707 version: 0.35.0-247707(@types/node@20.8.9) + '@playwright/test': + specifier: ^1.49.1 + version: 1.49.1 '@types/node': specifier: ^20.8.9 version: 20.8.9 @@ -2158,6 +2161,9 @@ importers: '@electron-toolkit/tsconfig': specifier: ^1.0.1 version: 1.0.1(@types/node@18.18.7) + '@playwright/test': + specifier: ^1.49.1 + version: 1.49.1 '@types/debug': specifier: ^4.1.10 version: 4.1.10 @@ -3590,6 +3596,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@playwright/test@1.49.1': + resolution: {integrity: sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==} + engines: {node: '>=18'} + hasBin: true + '@puppeteer/browsers@2.4.1': resolution: {integrity: sha512-0kdAbmic3J09I6dT8e9vE2JOCSt13wHCW5x/ly8TSt2bDtuIWe2TgLZZDHdcziw9AVCzflMAXCrVyRIhIs44Ng==} engines: {node: '>=18'} @@ -5978,6 +5989,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -7665,6 +7681,16 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + playwright-core@1.49.1: + resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.49.1: + resolution: {integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==} + engines: {node: '>=18'} + hasBin: true + plist@3.1.0: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} @@ -11118,6 +11144,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.49.1': + dependencies: + playwright: 1.49.1 + '@puppeteer/browsers@2.4.1': dependencies: debug: 4.3.7(supports-color@8.1.1) @@ -14084,6 +14114,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -16019,6 +16052,14 @@ snapshots: dependencies: find-up: 4.1.0 + playwright-core@1.49.1: {} + + playwright@1.49.1: + dependencies: + playwright-core: 1.49.1 + optionalDependencies: + fsevents: 2.3.2 + plist@3.1.0: dependencies: '@xmldom/xmldom': 0.8.10 From 1cd971df487e8097e6ca7745fa8c613927e1d247 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 22 Jan 2025 10:15:29 -0800 Subject: [PATCH 002/138] Added playwright extension --- ts/.vscode/extensions.json | 3 ++- ts/pnpm-lock.yaml | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ts/.vscode/extensions.json b/ts/.vscode/extensions.json index 63739a0ef..3c2cb7cb5 100644 --- a/ts/.vscode/extensions.json +++ b/ts/.vscode/extensions.json @@ -1,6 +1,7 @@ { "recommendations": [ "esbenp.prettier-vscode", - "Orta.vscode-jest" + "Orta.vscode-jest", + "ms-playwright.playwright" ] } diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index 11100330b..b41af30cd 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@fluidframework/build-tools': specifier: ^0.35.0-247707 version: 0.35.0-247707(@types/node@20.8.9) - '@playwright/test': - specifier: ^1.49.1 - version: 1.49.1 '@types/node': specifier: ^20.8.9 version: 20.8.9 From a988eed993d86bca44d08d020579e10a779a5273 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 22 Jan 2025 15:41:50 -0800 Subject: [PATCH 003/138] Allow setting of complex objects --- ts/packages/shell/src/main/agent.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ts/packages/shell/src/main/agent.ts b/ts/packages/shell/src/main/agent.ts index dfe7b082b..b5e02823e 100644 --- a/ts/packages/shell/src/main/agent.ts +++ b/ts/packages/shell/src/main/agent.ts @@ -100,7 +100,15 @@ class ShellSetSettingCommandHandler implements CommandHandler { for (const [key, v] of Object.entries(agentContext.settings)) { if (key === name) { found = true; - agentContext.settings.set(name, value); + if (typeof agentContext.settings[key] === "object") { + try { + agentContext.settings.set(name, value); + } catch (e) { + throw new Error(`Unable to set ${key} to ${value}. Details: ${e}`); + } + } else { + agentContext.settings.set(name, value); + } oldValue = v; break; } From 301b7c0f0913bbf130241b180795f8553c005403 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 22 Jan 2025 16:45:08 -0800 Subject: [PATCH 004/138] working on playwright tests. --- ts/packages/shell/playwright.config.ts | 118 +++++++++---------- ts/packages/shell/test/example.spec.ts | 135 ++++++++++------------ ts/packages/shell/test/hostWindow.spec.ts | 79 +++++++++++++ ts/packages/shell/test/testHelper.ts | 17 +++ 4 files changed, 213 insertions(+), 136 deletions(-) create mode 100644 ts/packages/shell/test/hostWindow.spec.ts create mode 100644 ts/packages/shell/test/testHelper.ts diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts index 0b5c698d5..1045a3141 100644 --- a/ts/packages/shell/playwright.config.ts +++ b/ts/packages/shell/playwright.config.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { defineConfig, devices } from '@playwright/test'; +import { defineConfig, devices } from "@playwright/test"; /** * Read environment variables from file. @@ -15,70 +15,70 @@ import { defineConfig, devices } from '@playwright/test'; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './test', - /* Run tests sequentially otherwise the client will complain about locked session file */ - fullyParallel: false, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://127.0.0.1:3000', + testDir: "./test", + /* Run tests sequentially otherwise the client will complain about locked session file */ + fullyParallel: false, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, - timeout: 120_000, // Set global timeout to 120 seconds + timeout: 120_000, // Set global timeout to 120 seconds - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, }); diff --git a/ts/packages/shell/test/example.spec.ts b/ts/packages/shell/test/example.spec.ts index eb581a1ee..a17905203 100644 --- a/ts/packages/shell/test/example.spec.ts +++ b/ts/packages/shell/test/example.spec.ts @@ -1,95 +1,76 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import test, { _electron as electron, expect } from '@playwright/test'; -import { defineConfig } from '@playwright/test'; -import fs from 'node:fs'; -import path from 'node:path'; +import test, { _electron as electron, expect } from "@playwright/test"; +import { getAppPath } from "./testHelper"; -export default defineConfig({ - timeout: 120_000, // Set global timeout to 120 seconds +test("launch app", async () => { + const electronApp = await electron.launch({ args: [getAppPath()] }); + // close app + await electronApp.close(); }); -/** - * Gets the correct path based on test context (cmdline vs. VSCode extension) - * @returns The root path to the project containing the playwright configuration - */ -function getAppPath(): string { - if (fs.existsSync('playwright.config.ts')) { - return '.'; - } else { - return path.join('.', 'packages/shell'); - } -} - -test('launch app', async () => { - const electronApp = await electron.launch({ args: [getAppPath()] }) - // close app - await electronApp.close() -}); - -test('get isPackaged', async () => { - const electronApp = await electron.launch({ args: [getAppPath()] }) - const isPackaged = await electronApp.evaluate(async ({ app }) => { - // This runs in Electron's main process, parameter here is always - // the result of the require('electron') in the main app script. - return app.isPackaged - }) - console.log(isPackaged) // false (because we're in development mode) - // close app - await electronApp.close() +test("get isPackaged", async () => { + const electronApp = await electron.launch({ args: [getAppPath()] }); + const isPackaged = await electronApp.evaluate(async ({ app }) => { + // This runs in Electron's main process, parameter here is always + // the result of the require('electron') in the main app script. + return app.isPackaged; + }); + console.log(isPackaged); // false (because we're in development mode) + // close app + await electronApp.close(); }); +test("launch app2", async () => { + // Launch Electron app. + const electronApp = await electron.launch({ args: [getAppPath()] }); -test('launch app2', async() => { - // Launch Electron app. - const electronApp = await electron.launch({ args: [getAppPath()] }); - - // Evaluation expression in the Electron context. - const appPath = await electronApp.evaluate(async ({ app }) => { - // This runs in the main Electron process, parameter here is always - // the result of the require('electron') in the main app script. - return app.getAppPath(); - }); - console.log(appPath); + // Evaluation expression in the Electron context. + const appPath = await electronApp.evaluate(async ({ app }) => { + // This runs in the main Electron process, parameter here is always + // the result of the require('electron') in the main app script. + return app.getAppPath(); + }); + console.log(appPath); - // Get the first window that the app opens, wait if necessary. - const window = await electronApp.firstWindow(); - // Print the title. - console.log(await window.title()); - // Capture a screenshot. - await window.screenshot({ path: 'test-results/intro.png' }); - // Direct Electron console to Node terminal. - window.on('console', console.log); - //// Click button. - //await window.click('text=Click me'); - // Exit app. - await electronApp.close(); + // Get the first window that the app opens, wait if necessary. + const window = await electronApp.firstWindow(); + // Print the title. + console.log(await window.title()); + // Capture a screenshot. + await window.screenshot({ path: "test-results/intro.png" }); + // Direct Electron console to Node terminal. + window.on("console", console.log); + //// Click button. + //await window.click('text=Click me'); + // Exit app. + await electronApp.close(); }); -test('save screenshot', async () => { - const electronApp = await electron.launch({ args: [getAppPath()] }) - const window = await electronApp.firstWindow() - await window.screenshot({ path: 'test-results/intro.png' }) - // close app - await electronApp.close() +test("save screenshot", async () => { + const electronApp = await electron.launch({ args: [getAppPath()] }); + const window = await electronApp.firstWindow(); + await window.screenshot({ path: "test-results/intro.png" }); + // close app + await electronApp.close(); }); -test('example test', async () => { - const electronApp = await electron.launch({ args: [getAppPath()] }) - const isPackaged = await electronApp.evaluate(async ({ app }) => { - // This runs in Electron's main process, parameter here is always - // the result of the require('electron') in the main app script. - return app.isPackaged - }) +test("example test", async () => { + const electronApp = await electron.launch({ args: [getAppPath()] }); + const isPackaged = await electronApp.evaluate(async ({ app }) => { + // This runs in Electron's main process, parameter here is always + // the result of the require('electron') in the main app script. + return app.isPackaged; + }); - expect(isPackaged).toBe(false) + expect(isPackaged).toBe(false); - // Wait for the first BrowserWindow to open - // and return its Page object - const window = await electronApp.firstWindow() - await window.screenshot({ path: 'test-results/intro.png' }) + // Wait for the first BrowserWindow to open + // and return its Page object + const window = await electronApp.firstWindow(); + await window.screenshot({ path: "test-results/intro.png" }); - // close app - await electronApp.close() + // close app + await electronApp.close(); }); diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts new file mode 100644 index 000000000..d65c71d9d --- /dev/null +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import test, { _electron, Browser, _electron as electron, expect, ViewportSize } from "@playwright/test"; +import { getAppPath } from "./testHelper"; +import { BrowserWindow, BrowserWindow as bw } from "electron"; + + +/** + * Test to ensure that the shell recall startup layout (position, size) + */ +test("remember window position", async () => { + const electronApp = await electron.launch({ args: [getAppPath()] }); + + electronApp.on('window', data => { + console.log(data); + }); + + // get the main window + const firstWindow = await electronApp.firstWindow(); + + // get the browser window handle for said window + const browser = await electronApp.browserWindow(firstWindow); + + // verify shell title + const title = await firstWindow.title(); + expect(title.indexOf("🤖") > -1, "Title expecting 🤖 but is missing."); + + // resize the shell + const size: ViewportSize = { width: Math.ceil(Math.random() * 800 + 200), height: Math.ceil(Math.random() * 800 + 200) }; + // //await window.setViewportSize(size); + // await firstWindow.setViewportSize(size); + await firstWindow.evaluate(` + // const { BrowserWindow } = require('electron'); + // BrowserWindow.getAllWindows().map((bw) => { + // bw.setBounds({ width: size.width, height: size.height }); + // }); + window.width = 100; + `); + + // const width = await browser.evaluate((w) => + // { + // eval("window.width = Math.ceil(Math.random() * 800 + 200);") + // return eval("window.width"); + // }); + // const height = await browser.evaluate((w) => + // { + // eval("window").height = Math.ceil(Math.random() * 800 + 200); + // return w.height; + // }); + + // move the shell somewhere + const x: number = await browser.evaluate((w) => { + w.screenX = Math.ceil(Math.random() * 100); + return w.screenX; + }); + const y: number = await browser.evaluate((w) => { + w.screenY = Math.ceil(Math.random() * 100); + return w.screenY; + }); + + // close the application + await electronApp.close(); + + // restart the app + const newElectronApp = await electron.launch({ args: [getAppPath()] }); + const newWindow = await newElectronApp.firstWindow(); + const newBrowser = await newElectronApp.browserWindow(newWindow); + + // get the shell size and location + const newSize: ViewportSize = newWindow.viewportSize()!; + const newX: number = await newBrowser.evaluate((w) => { return w.screenX; }); + const newY: number = await newBrowser.evaluate((w) => { return w.screenY; }); + + expect(newSize.height == size.height, "Window height mismatch!"); + expect(newSize.width == size.width, "Window width mismatch!"); + expect(newX == x, "X position mismatch!"); + expect(newY == y, "Y position mismatch!"); +}); \ No newline at end of file diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts new file mode 100644 index 000000000..cc8005c24 --- /dev/null +++ b/ts/packages/shell/test/testHelper.ts @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import fs from "node:fs"; +import path from "node:path"; + +/** + * Gets the correct path based on test context (cmdline vs. VSCode extension) + * @returns The root path to the project containing the playwright configuration + */ +export function getAppPath(): string { + if (fs.existsSync("playwright.config.ts")) { + return "."; + } else { + return path.join(".", "packages/shell"); + } +} From 07ff7e84ad8d7549148fb5dabb2669ba3a7cb523 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 22 Jan 2025 21:01:34 -0800 Subject: [PATCH 005/138] updated so that "@shell set " will move and resize window --- ts/packages/shell/src/main/index.ts | 11 ++++++++++- ts/packages/shell/src/main/shellSettings.ts | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ts/packages/shell/src/main/index.ts b/ts/packages/shell/src/main/index.ts index 935a22be2..0f2f76f5b 100644 --- a/ts/packages/shell/src/main/index.ts +++ b/ts/packages/shell/src/main/index.ts @@ -228,11 +228,17 @@ function createWindow(): void { }); // Notify renderer process whenever settings are modified - ShellSettings.getinstance().onSettingsChanged = (): void => { + ShellSettings.getinstance().onSettingsChanged = (settingName?: string | undefined): void => { chatView?.webContents.send( "settings-changed", ShellSettings.getinstance().getSerializable(), ); + + if (settingName == "size") { + mainWindow?.setSize(ShellSettings.getinstance().width, ShellSettings.getinstance().height); + } else if (settingName == "position") { + mainWindow?.setPosition(ShellSettings.getinstance().x!, ShellSettings.getinstance().y!); + } }; ShellSettings.getinstance().onShowSettingsDialog = ( @@ -582,6 +588,9 @@ async function initialize() { ShellSettings.getinstance().agentGreeting = settings.agentGreeting; ShellSettings.getinstance().partialCompletion = settings.partialCompletion; + ShellSettings.getinstance().darkMode = settings.darkMode; + + // write the settings to disk ShellSettings.getinstance().save(); }); diff --git a/ts/packages/shell/src/main/shellSettings.ts b/ts/packages/shell/src/main/shellSettings.ts index 932188534..f6c257c53 100644 --- a/ts/packages/shell/src/main/shellSettings.ts +++ b/ts/packages/shell/src/main/shellSettings.ts @@ -37,7 +37,7 @@ export class ShellSettings public devUI: boolean; public partialCompletion: boolean; public disallowedDisplayType: DisplayType[]; - public onSettingsChanged: EmptyFunction | null; + public onSettingsChanged: ((name?: string | undefined) => void) | null; public onShowSettingsDialog: ((dialogName: string) => void) | null; public onRunDemo: ((interactive: boolean) => void) | null; public onToggleTopMost: EmptyFunction | null; @@ -157,7 +157,7 @@ export class ShellSettings } if (ShellSettings.getinstance().onSettingsChanged != null) { - ShellSettings.getinstance().onSettingsChanged!(); + ShellSettings.getinstance().onSettingsChanged!(name); } } From cc541738d66d24a2a440eb67a74b1c4db1cacc52 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 22 Jan 2025 23:04:46 -0800 Subject: [PATCH 006/138] 1st test working! --- .../shell/src/renderer/assets/styles.less | 4 + .../src/renderer/src/messageContainer.ts | 3 +- .../shell/src/renderer/src/setContent.ts | 5 +- ts/packages/shell/test/hostWindow.spec.ts | 77 +++++++++---------- ts/packages/shell/test/testHelper.ts | 50 ++++++++++++ 5 files changed, 97 insertions(+), 42 deletions(-) diff --git a/ts/packages/shell/src/renderer/assets/styles.less b/ts/packages/shell/src/renderer/assets/styles.less index 73eaadbc9..8523edef6 100644 --- a/ts/packages/shell/src/renderer/assets/styles.less +++ b/ts/packages/shell/src/renderer/assets/styles.less @@ -302,6 +302,10 @@ body { white-space: break-spaces; } +.chat-message-user-text { + white-space: break-spaces; +} + table.table-message { table-layout: fixed; text-overflow: ellipsis; diff --git a/ts/packages/shell/src/renderer/src/messageContainer.ts b/ts/packages/shell/src/renderer/src/messageContainer.ts index 6e49f1274..9a91ae8c2 100644 --- a/ts/packages/shell/src/renderer/src/messageContainer.ts +++ b/ts/packages/shell/src/renderer/src/messageContainer.ts @@ -248,9 +248,10 @@ export class MessageContainer { this.messageDiv, content, this.settingsView, + this.classNameSuffix, appendMode === "inline" && this.lastAppendMode !== "inline" ? "block" - : appendMode, + : appendMode, ); this.speak(speakText, appendMode); diff --git a/ts/packages/shell/src/renderer/src/setContent.ts b/ts/packages/shell/src/renderer/src/setContent.ts index 57e22ef36..d93b3560a 100644 --- a/ts/packages/shell/src/renderer/src/setContent.ts +++ b/ts/packages/shell/src/renderer/src/setContent.ts @@ -99,6 +99,7 @@ export function setContent( elm: HTMLElement, content: DisplayContent, settingsView: SettingsView, + classNameModifier: string, appendMode?: DisplayAppendMode, ): string | undefined { // Remove existing content if we are not appending. @@ -152,13 +153,13 @@ export function setContent( if (type === "text") { const prevElm = contentDiv.lastChild as HTMLElement | null; - if (prevElm?.classList.contains("chat-message-agent-text")) { + if (prevElm?.classList.contains(`chat-message-${classNameModifier}-text`)) { // If there is an existing text element then append to it. contentElm = prevElm; } else { const span = document.createElement("span"); // create a text span so we can set "whitespace: break-spaces" css style of text content. - span.className = `chat-message-agent-text`; + span.className = `chat-message-${classNameModifier}-text`; contentDiv.appendChild(span); contentElm = span; } diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index d65c71d9d..cef4ea136 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import test, { _electron, Browser, _electron as electron, expect, ViewportSize } from "@playwright/test"; -import { getAppPath } from "./testHelper"; +import { getAppPath, getLastAgentMessage, sendUserRequest, waitForAgentMessage } from "./testHelper"; import { BrowserWindow, BrowserWindow as bw } from "electron"; @@ -26,38 +26,25 @@ test("remember window position", async () => { const title = await firstWindow.title(); expect(title.indexOf("🤖") > -1, "Title expecting 🤖 but is missing."); - // resize the shell - const size: ViewportSize = { width: Math.ceil(Math.random() * 800 + 200), height: Math.ceil(Math.random() * 800 + 200) }; - // //await window.setViewportSize(size); - // await firstWindow.setViewportSize(size); - await firstWindow.evaluate(` - // const { BrowserWindow } = require('electron'); - // BrowserWindow.getAllWindows().map((bw) => { - // bw.setBounds({ width: size.width, height: size.height }); - // }); - window.width = 100; - `); - - // const width = await browser.evaluate((w) => - // { - // eval("window.width = Math.ceil(Math.random() * 800 + 200);") - // return eval("window.width"); - // }); - // const height = await browser.evaluate((w) => - // { - // eval("window").height = Math.ceil(Math.random() * 800 + 200); - // return w.height; - // }); - - // move the shell somewhere - const x: number = await browser.evaluate((w) => { - w.screenX = Math.ceil(Math.random() * 100); - return w.screenX; - }); - const y: number = await browser.evaluate((w) => { - w.screenY = Math.ceil(Math.random() * 100); - return w.screenY; - }); + // wait for agent greeting + await waitForAgentMessage(firstWindow, 10000, 1); + + // resize the shell by sending @shell settings set size "[width, height]" + const width: number = Math.ceil(Math.random() * 800 + 200); + const height: number = Math.ceil(Math.random() * 800 + 200); + await sendUserRequest(`@shell set size "[${width}, ${height}]"`, firstWindow); + + // wait for agent response + await waitForAgentMessage(firstWindow, 10000, 2); + + // move the window + const x: number = Math.ceil(Math.random() * 100); + const y: number = Math.ceil(Math.random() * 100); + + await sendUserRequest(`@shell set position "[${x}, ${y}]"`, firstWindow); + + // wait for agent response + await waitForAgentMessage(firstWindow, 10000, 3); // close the application await electronApp.close(); @@ -67,13 +54,25 @@ test("remember window position", async () => { const newWindow = await newElectronApp.firstWindow(); const newBrowser = await newElectronApp.browserWindow(newWindow); - // get the shell size and location - const newSize: ViewportSize = newWindow.viewportSize()!; - const newX: number = await newBrowser.evaluate((w) => { return w.screenX; }); - const newY: number = await newBrowser.evaluate((w) => { return w.screenY; }); + // wait for agent greeting + await waitForAgentMessage(newWindow, 10000, 1); + + // get window size/position + await sendUserRequest(`@shell show raw`, newWindow); + + // wait for agent response + await waitForAgentMessage(newWindow, 10000, 2); + + // get the shell size and location from the raw settings + const msg =await getLastAgentMessage(newWindow); + const lines: string[] = msg.split("\n"); + const newWidth: number = parseInt(lines[1].split(":")[1].trim()); + const newHeight: number = parseInt(lines[2].split(":")[1].trim()); + const newX: number = parseInt(lines[4].split(":")[1].trim()); + const newY: number = parseInt(lines[5].split(":")[1].trim()); - expect(newSize.height == size.height, "Window height mismatch!"); - expect(newSize.width == size.width, "Window width mismatch!"); + expect(newHeight == height, "Window height mismatch!"); + expect(newWidth == width, "Window width mismatch!"); expect(newX == x, "X position mismatch!"); expect(newY == y, "Y position mismatch!"); }); \ No newline at end of file diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index cc8005c24..1062252b1 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { ElectronApplication, Locator, Page } from "@playwright/test"; import fs from "node:fs"; import path from "node:path"; @@ -15,3 +16,52 @@ export function getAppPath(): string { return path.join(".", "packages/shell"); } } + +/** + * Submits a user request to the system via the chat input box. + * @param prompt The user request/prompt. + * @param page The maing page from the electron host application. + */ +export async function sendUserRequest(prompt: string, page: Page) { + const element = await page.waitForSelector("#phraseDiv"); + await element.focus(); + await element.fill(prompt); + await element.press("Enter"); +} + +/** + * Gets the last agent message from the chat view + * @param page The maing page from the electron host application. + */ +export async function getLastAgentMessage(page: Page): Promise { + const locators: Locator[] = await page.locator('.chat-message-agent-text').all(); + + return locators[0].innerText(); +} + +/** + * + * @param page The page where the chatview is hosted + * @param timeout The maximum amount of time to wait for the agent message + * @param expectedMessageCount The expected # of agent messages at this time. + * @returns When the expected # of messages is reached or the timeout is reached. Whichever occurrs first. + */ +export async function waitForAgentMessage(page: Page, timeout: number, expectedMessageCount?: number | undefined): Promise { + let timeWaited = 0; + let locators: Locator[] = await page.locator('.chat-message-agent-text').all(); + let originalAgentMessageCount = locators.length; + let messageCount = originalAgentMessageCount; + + if (expectedMessageCount == messageCount) { + return; + } + + do { + await page.waitForTimeout(1000); + timeWaited += 1000; + + locators = await page.locator('.chat-message-agent-text').all(); + messageCount = locators.length; + + } while (timeWaited <= timeout && messageCount == originalAgentMessageCount); +} From c8a7a07263db6a280318a97caae0f8dc59d97359 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 23 Jan 2025 00:53:45 -0800 Subject: [PATCH 007/138] added zoom test (not yet woring) --- ts/packages/shell/src/renderer/src/main.ts | 12 ++- ts/packages/shell/test/hostWindow.spec.ts | 106 ++++++++++++++++----- 2 files changed, 92 insertions(+), 26 deletions(-) diff --git a/ts/packages/shell/src/renderer/src/main.ts b/ts/packages/shell/src/renderer/src/main.ts index 5eddcbcc2..981c633f7 100644 --- a/ts/packages/shell/src/renderer/src/main.ts +++ b/ts/packages/shell/src/renderer/src/main.ts @@ -227,7 +227,11 @@ function addEvents( }); api.onSettingSummaryChanged((_, summary, registeredAgents) => { document.title = summary; - document.title += ` Zoom: ${settingsView.shellSettings.zoomLevel * 100}%`; + if (settingsView.shellSettings.zoomLevel != 0) { + document.title += ` Zoom: ${settingsView.shellSettings.zoomLevel * 100}%`; + } else { + document.title += ` Zoom: 100%`; + } agents.clear(); for (let key of registeredAgents.keys()) { @@ -257,7 +261,11 @@ function addEvents( document.title.indexOf("Zoom: "), ); - document.title = `${newTitle} Zoom: ${value.zoomLevel * 100}%`; + if (value.zoomLevel != 0) { + document.title = `${newTitle} Zoom: ${value.zoomLevel * 100}%`; + } else { + document.title = `${newTitle} Zoom: 100%`; + } settingsView.shellSettings = value; }); diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index cef4ea136..1aec678c9 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -1,38 +1,45 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import test, { _electron, Browser, _electron as electron, expect, ViewportSize } from "@playwright/test"; -import { getAppPath, getLastAgentMessage, sendUserRequest, waitForAgentMessage } from "./testHelper"; -import { BrowserWindow, BrowserWindow as bw } from "electron"; - +import test, { + _electron, + Browser, + _electron as electron, + expect, + Page, + ViewportSize, +} from "@playwright/test"; +import { + getAppPath, + getLastAgentMessage, + sendUserRequest, + waitForAgentMessage, +} from "./testHelper"; /** * Test to ensure that the shell recall startup layout (position, size) */ test("remember window position", async () => { + // start the app const electronApp = await electron.launch({ args: [getAppPath()] }); - electronApp.on('window', data => { - console.log(data); - }); - // get the main window - const firstWindow = await electronApp.firstWindow(); + const firstWindow: Page = await electronApp.firstWindow(); - // get the browser window handle for said window - const browser = await electronApp.browserWindow(firstWindow); + // wait for agent greeting + await waitForAgentMessage(firstWindow, 10000, 1); // verify shell title const title = await firstWindow.title(); expect(title.indexOf("🤖") > -1, "Title expecting 🤖 but is missing."); - // wait for agent greeting - await waitForAgentMessage(firstWindow, 10000, 1); - // resize the shell by sending @shell settings set size "[width, height]" const width: number = Math.ceil(Math.random() * 800 + 200); - const height: number = Math.ceil(Math.random() * 800 + 200); - await sendUserRequest(`@shell set size "[${width}, ${height}]"`, firstWindow); + const height: number = Math.ceil(Math.random() * 800 + 200); + await sendUserRequest( + `@shell set size "[${width}, ${height}]"`, + firstWindow, + ); // wait for agent response await waitForAgentMessage(firstWindow, 10000, 2); @@ -51,8 +58,7 @@ test("remember window position", async () => { // restart the app const newElectronApp = await electron.launch({ args: [getAppPath()] }); - const newWindow = await newElectronApp.firstWindow(); - const newBrowser = await newElectronApp.browserWindow(newWindow); + const newWindow: Page = await newElectronApp.firstWindow(); // wait for agent greeting await waitForAgentMessage(newWindow, 10000, 1); @@ -64,15 +70,67 @@ test("remember window position", async () => { await waitForAgentMessage(newWindow, 10000, 2); // get the shell size and location from the raw settings - const msg =await getLastAgentMessage(newWindow); + const msg = await getLastAgentMessage(newWindow); const lines: string[] = msg.split("\n"); const newWidth: number = parseInt(lines[1].split(":")[1].trim()); const newHeight: number = parseInt(lines[2].split(":")[1].trim()); const newX: number = parseInt(lines[4].split(":")[1].trim()); const newY: number = parseInt(lines[5].split(":")[1].trim()); + + expect(newHeight, `Window height mismatch! Expected ${height} got ${height}`).toBe(newHeight); + expect(newWidth, `Window width mismatch! Expected ${width} got ${width}`).toBe(newWidth); + expect(newX, `X position mismatch! Expected ${x} got ${newX}`).toBe(x); + expect(newY, `Y position mismatch!Expected ${y} got ${newY}`).toBe(y); +}); + +/** + * Ensures zoom level is working + */ +test("zoom level", async () => { + // start the app + const electronApp = await electron.launch({ args: [getAppPath()] }); + + // get the main window + const mainWindow = await electronApp.firstWindow(); + + // wait for agent greeting + await waitForAgentMessage(mainWindow, 10000, 1); + + // get the title + let title = await mainWindow.title(); + + // get zoom level out of title + let subTitle: string = title.match(/\d+%/)![0]; + const origZoomLevel: number = parseInt(subTitle.substring(0, subTitle.length - 1)); + + // // Focus on the body + // await mainWindow.focus('body'); + + const e = await mainWindow.waitForSelector(".chat-container"); +await e.focus(); + // // zoom in + // // await mainWindow.press('body', "CTRL+Plus"); + // await mainWindow.keyboard.down('Control'); + // //await mainWindow.keyboard.press('+'); + // await mainWindow.mouse.wheel(0, 5); + // await mainWindow.keyboard.up('Control'); + // await mainWindow.keyboard.press("Control+-"); + + // // for title update + // await mainWindow.waitForTimeout(1000); + // //await main + // //await mainWindow.press('@document', "Control++"); + await e.press("Control+-"); + + // get the title + title = await mainWindow.title(); + + // get zoom level out of title + subTitle = title.match(/\d+%/)![0]; + const newZoomLevel: number = parseInt(subTitle.substring(0, subTitle.length - 1)); - expect(newHeight == height, "Window height mismatch!"); - expect(newWidth == width, "Window width mismatch!"); - expect(newX == x, "X position mismatch!"); - expect(newY == y, "Y position mismatch!"); -}); \ No newline at end of file + expect(newZoomLevel, `Zoom not functioning as expected. Expected ${origZoomLevel}% got ${newZoomLevel}%`).toBeGreaterThan(origZoomLevel); + + // close the application + await electronApp.close(); +}); From 835f7b336897c81fec7302dbf2dc33a75fed309f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 23 Jan 2025 12:15:27 -0800 Subject: [PATCH 008/138] Changed to use zoomFactor instead of zoomLevel. --- ts/packages/shell/src/main/index.ts | 39 ++++---- ts/packages/shell/src/main/shellSettings.ts | 3 +- .../shell/src/renderer/src/chatInput.ts | 1 + ts/packages/shell/test/hostWindow.spec.ts | 96 ++++++++++++------- 4 files changed, 85 insertions(+), 54 deletions(-) diff --git a/ts/packages/shell/src/main/index.ts b/ts/packages/shell/src/main/index.ts index 0f2f76f5b..d0e7f38be 100644 --- a/ts/packages/shell/src/main/index.ts +++ b/ts/packages/shell/src/main/index.ts @@ -239,6 +239,10 @@ function createWindow(): void { } else if (settingName == "position") { mainWindow?.setPosition(ShellSettings.getinstance().x!, ShellSettings.getinstance().y!); } + + if (settingName == "zoomLevel") { + setZoomLevel(ShellSettings.getinstance().zoomLevel, chatView); + } }; ShellSettings.getinstance().onShowSettingsDialog = ( @@ -705,32 +709,29 @@ app.on("window-all-closed", () => { }); function zoomIn(chatView: BrowserView) { - const curr = chatView.webContents.zoomLevel; - chatView.webContents.zoomLevel = Math.min(curr + 0.5, 9); - - ShellSettings.getinstance().set( - "zoomLevel", - chatView.webContents.zoomLevel, - ); - - updateZoomInTitle(chatView); + setZoomLevel(chatView.webContents.zoomFactor + 0.1, chatView); } function zoomOut(chatView: BrowserView) { - const curr = chatView.webContents.zoomLevel; - chatView.webContents.zoomLevel = Math.max(curr - 0.5, -8); - ShellSettings.getinstance().set( - "zoomLevel", - chatView.webContents.zoomLevel, - ); + setZoomLevel(chatView.webContents.zoomFactor - 0.1, chatView); +} + +function setZoomLevel(zoomLevel: number, chatView: BrowserView | null) { + + if (zoomLevel < 0.1) { + zoomLevel = 0.1 + } else if (zoomLevel > 10) { + zoomLevel = 10; + } + + chatView!.webContents.zoomFactor = zoomLevel; + ShellSettings.getinstance().set("zoomLevel", zoomLevel); - updateZoomInTitle(chatView); + updateZoomInTitle(chatView!); } function resetZoom(chatView: BrowserView) { - chatView.webContents.zoomLevel = 0; - ShellSettings.getinstance().set("zoomLevel", 0); - updateZoomInTitle(chatView); + setZoomLevel(1, chatView); } function updateZoomInTitle(chatView: BrowserView) { diff --git a/ts/packages/shell/src/main/shellSettings.ts b/ts/packages/shell/src/main/shellSettings.ts index f6c257c53..4959b8a76 100644 --- a/ts/packages/shell/src/main/shellSettings.ts +++ b/ts/packages/shell/src/main/shellSettings.ts @@ -133,6 +133,7 @@ export class ShellSettings public set(name: string, value: any) { const t = typeof ShellSettings.getinstance()[name]; + const oldValue = ShellSettings.getinstance()[name]; if (t === typeof value) { ShellSettings.getinstance()[name] = value; @@ -156,7 +157,7 @@ export class ShellSettings } } - if (ShellSettings.getinstance().onSettingsChanged != null) { + if (ShellSettings.getinstance().onSettingsChanged != null && oldValue != value) { ShellSettings.getinstance().onSettingsChanged!(name); } } diff --git a/ts/packages/shell/src/renderer/src/chatInput.ts b/ts/packages/shell/src/renderer/src/chatInput.ts index a1ea40d55..98b5266bf 100644 --- a/ts/packages/shell/src/renderer/src/chatInput.ts +++ b/ts/packages/shell/src/renderer/src/chatInput.ts @@ -168,6 +168,7 @@ export class ChatInput { this.inputContainer = document.createElement("div"); this.inputContainer.className = "chat-input"; this.sendButton = document.createElement("button"); + this.sendButton.id = "sendbutton"; this.sendButton.appendChild(iconSend()); this.sendButton.className = "chat-input-button"; this.sendButton.onclick = () => { diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index 1aec678c9..b2044e480 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -15,11 +15,14 @@ import { sendUserRequest, waitForAgentMessage, } from "./testHelper"; +import { send } from "node:process"; /** * Test to ensure that the shell recall startup layout (position, size) */ test("remember window position", async () => { + let agentMessageCount = 0; + // start the app const electronApp = await electron.launch({ args: [getAppPath()] }); @@ -27,7 +30,7 @@ test("remember window position", async () => { const firstWindow: Page = await electronApp.firstWindow(); // wait for agent greeting - await waitForAgentMessage(firstWindow, 10000, 1); + await waitForAgentMessage(firstWindow, 10000, ++agentMessageCount); // verify shell title const title = await firstWindow.title(); @@ -42,7 +45,7 @@ test("remember window position", async () => { ); // wait for agent response - await waitForAgentMessage(firstWindow, 10000, 2); + await waitForAgentMessage(firstWindow, 10000, ++agentMessageCount); // move the window const x: number = Math.ceil(Math.random() * 100); @@ -51,7 +54,7 @@ test("remember window position", async () => { await sendUserRequest(`@shell set position "[${x}, ${y}]"`, firstWindow); // wait for agent response - await waitForAgentMessage(firstWindow, 10000, 3); + await waitForAgentMessage(firstWindow, 10000, ++agentMessageCount); // close the application await electronApp.close(); @@ -61,13 +64,14 @@ test("remember window position", async () => { const newWindow: Page = await newElectronApp.firstWindow(); // wait for agent greeting - await waitForAgentMessage(newWindow, 10000, 1); + agentMessageCount = 0; + await waitForAgentMessage(newWindow, 10000, ++agentMessageCount); // get window size/position await sendUserRequest(`@shell show raw`, newWindow); // wait for agent response - await waitForAgentMessage(newWindow, 10000, 2); + await waitForAgentMessage(newWindow, 10000, ++agentMessageCount); // get the shell size and location from the raw settings const msg = await getLastAgentMessage(newWindow); @@ -87,6 +91,9 @@ test("remember window position", async () => { * Ensures zoom level is working */ test("zoom level", async () => { + + let agentMessageCount = 0; + // start the app const electronApp = await electron.launch({ args: [getAppPath()] }); @@ -94,42 +101,63 @@ test("zoom level", async () => { const mainWindow = await electronApp.firstWindow(); // wait for agent greeting - await waitForAgentMessage(mainWindow, 10000, 1); + await waitForAgentMessage(mainWindow, 10000, ++agentMessageCount); + + // test 80% zoom + await testZoomLevel(0.8, mainWindow, agentMessageCount++); + + // set the zoom level to 120% + await testZoomLevel(1.2, mainWindow, agentMessageCount++); + + // reset zoomLevel + await testZoomLevel(1, mainWindow, agentMessageCount++); + + // close the application + await electronApp.close(); +}); + +async function testZoomLevel(level: number, page: Page, agentMessageCount: number) { + + // set the zoom level to 80% + await sendUserRequest(`@shell set zoomLevel ${level}`, page); + + // wait for agent response + await waitForAgentMessage(page, 10000, ++agentMessageCount); // get the title - let title = await mainWindow.title(); + let title = await page.title(); // get zoom level out of title let subTitle: string = title.match(/\d+%/)![0]; - const origZoomLevel: number = parseInt(subTitle.substring(0, subTitle.length - 1)); - - // // Focus on the body - // await mainWindow.focus('body'); - - const e = await mainWindow.waitForSelector(".chat-container"); -await e.focus(); - // // zoom in - // // await mainWindow.press('body', "CTRL+Plus"); - // await mainWindow.keyboard.down('Control'); - // //await mainWindow.keyboard.press('+'); - // await mainWindow.mouse.wheel(0, 5); - // await mainWindow.keyboard.up('Control'); - // await mainWindow.keyboard.press("Control+-"); - - // // for title update - // await mainWindow.waitForTimeout(1000); - // //await main - // //await mainWindow.press('@document', "Control++"); - await e.press("Control+-"); + let zoomLevel: number = parseInt(subTitle.substring(0, subTitle.length - 1)); - // get the title - title = await mainWindow.title(); + expect(zoomLevel, `Unexpected zoomLevel, expected ${level * 100}, got ${zoomLevel}`).toBe(level * 100); +} - // get zoom level out of title - subTitle = title.match(/\d+%/)![0]; - const newZoomLevel: number = parseInt(subTitle.substring(0, subTitle.length - 1)); - - expect(newZoomLevel, `Zoom not functioning as expected. Expected ${origZoomLevel}% got ${newZoomLevel}%`).toBeGreaterThan(origZoomLevel); +/** + * Ensure send button is behaving + */ +test("send button state", async () => { + let agentMessageCount = 0; + + // start the app + const electronApp = await electron.launch({ args: [getAppPath()] }); + + // get the main window + const mainWindow = await electronApp.firstWindow(); + + // // wait for agent greeting + // await waitForAgentMessage(mainWindow, 10000, ++agentMessageCount); + + // make sure send button is disabled + const sendButton = await mainWindow.locator("#sendbutton"); + await expect(sendButton, "Send button expected to be disabled.").toBeDisabled(); + + // put some text in the text box + const element = await mainWindow.waitForSelector("#phraseDiv"); + await element.fill("This is a test..."); + + await expect(sendButton, "Send button expected to be enabled.").toBeEnabled(); // close the application await electronApp.close(); From a27d2de332c8aa98d6450df692b9f1b03bc90a94 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 23 Jan 2025 15:38:45 -0800 Subject: [PATCH 009/138] config tests --- ts/packages/shell/test/configCommands.spec.ts | 44 +++++++++++ ts/packages/shell/test/hostWindow.spec.ts | 79 ++++++------------- ts/packages/shell/test/testHelper.ts | 69 +++++++++++++++- 3 files changed, 135 insertions(+), 57 deletions(-) create mode 100644 ts/packages/shell/test/configCommands.spec.ts diff --git a/ts/packages/shell/test/configCommands.spec.ts b/ts/packages/shell/test/configCommands.spec.ts new file mode 100644 index 000000000..d382fcfe9 --- /dev/null +++ b/ts/packages/shell/test/configCommands.spec.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import test, { + _electron, + _electron as electron, + expect, + Page, +} from "@playwright/test"; +import { + exitApplication, + getAppPath, + getLastAgentMessage, + sendUserRequest, + sendUserRequestAndWaitForResponse, + testSetup, + waitForAgentMessage, +} from "./testHelper"; + +test("@config dev", async () => { + + // launch the app + const mainWindow: Page = await testSetup(); + + await sendUserRequestAndWaitForResponse(`@config dev`, mainWindow); + let msg = await getLastAgentMessage(mainWindow); + + expect(msg.toLowerCase(), "Dev mode was not turned on as expected.").toBe("development mode is enabled.") + + await sendUserRequestAndWaitForResponse(`@config dev on`, mainWindow); + msg = await getLastAgentMessage(mainWindow); + + expect(msg.toLowerCase(), "Dev mode was not turned on as expected.").toBe("development mode is enabled.") + + await sendUserRequestAndWaitForResponse(`@config dev off`, mainWindow); + msg = await getLastAgentMessage(mainWindow); + + expect(msg.toLowerCase(), "Dev mode was not turned off as expected.").toBe("development mode is disabled.") + + // close the application + await exitApplication(mainWindow); +}); + +// TODO: Test action correction \ No newline at end of file diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index b2044e480..726a2bfeb 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -3,19 +3,21 @@ import test, { _electron, - Browser, _electron as electron, expect, Page, - ViewportSize, } from "@playwright/test"; import { + exitApplication, getAppPath, getLastAgentMessage, sendUserRequest, + sendUserRequestAndWaitForResponse, + sendUserRequestFast, + testSetup, waitForAgentMessage, } from "./testHelper"; -import { send } from "node:process"; +import { exit } from "process"; /** * Test to ensure that the shell recall startup layout (position, size) @@ -23,14 +25,7 @@ import { send } from "node:process"; test("remember window position", async () => { let agentMessageCount = 0; - // start the app - const electronApp = await electron.launch({ args: [getAppPath()] }); - - // get the main window - const firstWindow: Page = await electronApp.firstWindow(); - - // wait for agent greeting - await waitForAgentMessage(firstWindow, 10000, ++agentMessageCount); + const firstWindow = await testSetup(); // verify shell title const title = await firstWindow.title(); @@ -39,39 +34,25 @@ test("remember window position", async () => { // resize the shell by sending @shell settings set size "[width, height]" const width: number = Math.ceil(Math.random() * 800 + 200); const height: number = Math.ceil(Math.random() * 800 + 200); - await sendUserRequest( + await sendUserRequestAndWaitForResponse( `@shell set size "[${width}, ${height}]"`, firstWindow, ); - // wait for agent response - await waitForAgentMessage(firstWindow, 10000, ++agentMessageCount); - // move the window const x: number = Math.ceil(Math.random() * 100); const y: number = Math.ceil(Math.random() * 100); - await sendUserRequest(`@shell set position "[${x}, ${y}]"`, firstWindow); - - // wait for agent response - await waitForAgentMessage(firstWindow, 10000, ++agentMessageCount); + await sendUserRequestAndWaitForResponse(`@shell set position "[${x}, ${y}]"`, firstWindow); // close the application - await electronApp.close(); + await exitApplication(firstWindow); // restart the app - const newElectronApp = await electron.launch({ args: [getAppPath()] }); - const newWindow: Page = await newElectronApp.firstWindow(); - - // wait for agent greeting - agentMessageCount = 0; - await waitForAgentMessage(newWindow, 10000, ++agentMessageCount); + const newWindow: Page = await testSetup(); // get window size/position - await sendUserRequest(`@shell show raw`, newWindow); - - // wait for agent response - await waitForAgentMessage(newWindow, 10000, ++agentMessageCount); + await sendUserRequestAndWaitForResponse(`@shell show raw`, newWindow); // get the shell size and location from the raw settings const msg = await getLastAgentMessage(newWindow); @@ -85,6 +66,9 @@ test("remember window position", async () => { expect(newWidth, `Window width mismatch! Expected ${width} got ${width}`).toBe(newWidth); expect(newX, `X position mismatch! Expected ${x} got ${newX}`).toBe(x); expect(newY, `Y position mismatch!Expected ${y} got ${newY}`).toBe(y); + + // close the application + await exitApplication(newWindow); }); /** @@ -92,37 +76,26 @@ test("remember window position", async () => { */ test("zoom level", async () => { - let agentMessageCount = 0; - // start the app - const electronApp = await electron.launch({ args: [getAppPath()] }); - - // get the main window - const mainWindow = await electronApp.firstWindow(); - - // wait for agent greeting - await waitForAgentMessage(mainWindow, 10000, ++agentMessageCount); + const mainWindow = await testSetup(); // test 80% zoom - await testZoomLevel(0.8, mainWindow, agentMessageCount++); + await testZoomLevel(0.8, mainWindow); // set the zoom level to 120% - await testZoomLevel(1.2, mainWindow, agentMessageCount++); + await testZoomLevel(1.2, mainWindow); // reset zoomLevel - await testZoomLevel(1, mainWindow, agentMessageCount++); + await testZoomLevel(1, mainWindow); // close the application - await electronApp.close(); + await exitApplication(mainWindow); }); -async function testZoomLevel(level: number, page: Page, agentMessageCount: number) { +async function testZoomLevel(level: number, page: Page) { // set the zoom level to 80% - await sendUserRequest(`@shell set zoomLevel ${level}`, page); - - // wait for agent response - await waitForAgentMessage(page, 10000, ++agentMessageCount); + await sendUserRequestAndWaitForResponse(`@shell set zoomLevel ${level}`, page); // get the title let title = await page.title(); @@ -141,13 +114,7 @@ test("send button state", async () => { let agentMessageCount = 0; // start the app - const electronApp = await electron.launch({ args: [getAppPath()] }); - - // get the main window - const mainWindow = await electronApp.firstWindow(); - - // // wait for agent greeting - // await waitForAgentMessage(mainWindow, 10000, ++agentMessageCount); + const mainWindow = await testSetup(); // make sure send button is disabled const sendButton = await mainWindow.locator("#sendbutton"); @@ -160,5 +127,5 @@ test("send button state", async () => { await expect(sendButton, "Send button expected to be enabled.").toBeEnabled(); // close the application - await electronApp.close(); + await exitApplication(mainWindow); }); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 1062252b1..e451dad17 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -1,10 +1,51 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { ElectronApplication, Locator, Page } from "@playwright/test"; +import { + _electron, + _electron as electron, + ElectronApplication, + Locator, + Page } from "@playwright/test"; import fs from "node:fs"; import path from "node:path"; +let runningApplication: ElectronApplication | null = null; + +/** + * Starts the electron app and returns the main page after the greeting agent message has been posted. + */ +export async function testSetup(): Promise { + // agent message count + let agentMessageCount = 0; + + // start the app + if (runningApplication !== null) { + throw new Error("Application instance already running. Did you shutdown cleanly?"); + } + + runningApplication = await electron.launch({ args: [getAppPath()] }); + + // get the main window + const mainWindow: Page = await runningApplication.firstWindow(); + + // wait for agent greeting + await waitForAgentMessage(mainWindow, 10000, ++agentMessageCount); + + return mainWindow; +} + +/** + * Cleanly shuts down any running instance of the Shell + * @param page The main window of the application + */ +export async function exitApplication(page: Page): Promise { + await sendUserRequestFast("@exit", page); + + await runningApplication!.close(); + runningApplication = null; +} + /** * Gets the correct path based on test context (cmdline vs. VSCode extension) * @returns The root path to the project containing the playwright configuration @@ -29,6 +70,32 @@ export async function sendUserRequest(prompt: string, page: Page) { await element.press("Enter"); } +/** + * Submits a user request to the system via the chat input box without waiting. + * @param prompt The user request/prompt. + * @param page The maing page from the electron host application. + */ +export async function sendUserRequestFast(prompt: string, page: Page) { + const element = await page.waitForSelector("#phraseDiv"); + await element.fill(prompt); + page.keyboard.down("Enter"); +} + +/** + * Submits a user request to the system via the chat input box and then waits for the agent's response + * @param prompt The user request/prompt. + * @param page The maing page from the electron host application. + */ +export async function sendUserRequestAndWaitForResponse(prompt: string, page: Page) { + const locators: Locator[] = await page.locator('.chat-message-agent-text').all(); + + // send the user request + await sendUserRequest(prompt, page); + + // wait for agent response + await waitForAgentMessage(page, 10000, locators.length + 1); +} + /** * Gets the last agent message from the chat view * @param page The maing page from the electron host application. From 04fea285c1d278f92b66c0d2dfb41683f3ff994a Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 23 Jan 2025 19:08:19 -0800 Subject: [PATCH 010/138] added more tests, small refactor --- ts/packages/shell/test/configCommands.spec.ts | 31 +++++++++++++++---- ts/packages/shell/test/hostWindow.spec.ts | 3 +- ts/packages/shell/test/testHelper.ts | 5 ++- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/ts/packages/shell/test/configCommands.spec.ts b/ts/packages/shell/test/configCommands.spec.ts index d382fcfe9..2930777d7 100644 --- a/ts/packages/shell/test/configCommands.spec.ts +++ b/ts/packages/shell/test/configCommands.spec.ts @@ -22,18 +22,15 @@ test("@config dev", async () => { // launch the app const mainWindow: Page = await testSetup(); - await sendUserRequestAndWaitForResponse(`@config dev`, mainWindow); - let msg = await getLastAgentMessage(mainWindow); + let msg = await sendUserRequestAndWaitForResponse(`@config dev`, mainWindow); expect(msg.toLowerCase(), "Dev mode was not turned on as expected.").toBe("development mode is enabled.") - await sendUserRequestAndWaitForResponse(`@config dev on`, mainWindow); - msg = await getLastAgentMessage(mainWindow); + msg = await sendUserRequestAndWaitForResponse(`@config dev on`, mainWindow); expect(msg.toLowerCase(), "Dev mode was not turned on as expected.").toBe("development mode is enabled.") - await sendUserRequestAndWaitForResponse(`@config dev off`, mainWindow); - msg = await getLastAgentMessage(mainWindow); + msg = await sendUserRequestAndWaitForResponse(`@config dev off`, mainWindow); expect(msg.toLowerCase(), "Dev mode was not turned off as expected.").toBe("development mode is disabled.") @@ -41,4 +38,26 @@ test("@config dev", async () => { await exitApplication(mainWindow); }); +test("@config schema", async () => { + + // launch the app + const mainWindow: Page = await testSetup(); + + let msg = await sendUserRequestAndWaitForResponse(`@config schema oracle`, mainWindow); + + expect(msg.toLowerCase(), "Oracle scheme should be ON but it is OFF.").toContain("✅"); + + msg = await sendUserRequestAndWaitForResponse(`@config schema --off oracle`, mainWindow); + + expect(msg.toLowerCase(), "Oracle schema should be OFF but is is ON.").toContain("❌") + + msg = await sendUserRequestAndWaitForResponse(`@config dev off`, mainWindow); + + expect(msg.toLowerCase(), "Dev mode was not turned off as expected.").toBe("development mode is disabled.") + + // close the application + await exitApplication(mainWindow); + +}); + // TODO: Test action correction \ No newline at end of file diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index 726a2bfeb..ff0a571d3 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -52,10 +52,9 @@ test("remember window position", async () => { const newWindow: Page = await testSetup(); // get window size/position - await sendUserRequestAndWaitForResponse(`@shell show raw`, newWindow); + const msg = await sendUserRequestAndWaitForResponse(`@shell show raw`, newWindow); // get the shell size and location from the raw settings - const msg = await getLastAgentMessage(newWindow); const lines: string[] = msg.split("\n"); const newWidth: number = parseInt(lines[1].split(":")[1].trim()); const newHeight: number = parseInt(lines[2].split(":")[1].trim()); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index e451dad17..ff2d721d1 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -86,7 +86,7 @@ export async function sendUserRequestFast(prompt: string, page: Page) { * @param prompt The user request/prompt. * @param page The maing page from the electron host application. */ -export async function sendUserRequestAndWaitForResponse(prompt: string, page: Page) { +export async function sendUserRequestAndWaitForResponse(prompt: string, page: Page): Promise { const locators: Locator[] = await page.locator('.chat-message-agent-text').all(); // send the user request @@ -94,6 +94,9 @@ export async function sendUserRequestAndWaitForResponse(prompt: string, page: Pa // wait for agent response await waitForAgentMessage(page, 10000, locators.length + 1); + + // return the response + return await getLastAgentMessage(page); } /** From 88f3f78d748adf56efa0d7f0b374cb54925664a9 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 23 Jan 2025 19:59:14 -0800 Subject: [PATCH 011/138] removed example test file --- ts/packages/shell/test/example.spec.ts | 76 -------------------------- 1 file changed, 76 deletions(-) delete mode 100644 ts/packages/shell/test/example.spec.ts diff --git a/ts/packages/shell/test/example.spec.ts b/ts/packages/shell/test/example.spec.ts deleted file mode 100644 index a17905203..000000000 --- a/ts/packages/shell/test/example.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import test, { _electron as electron, expect } from "@playwright/test"; -import { getAppPath } from "./testHelper"; - -test("launch app", async () => { - const electronApp = await electron.launch({ args: [getAppPath()] }); - // close app - await electronApp.close(); -}); - -test("get isPackaged", async () => { - const electronApp = await electron.launch({ args: [getAppPath()] }); - const isPackaged = await electronApp.evaluate(async ({ app }) => { - // This runs in Electron's main process, parameter here is always - // the result of the require('electron') in the main app script. - return app.isPackaged; - }); - console.log(isPackaged); // false (because we're in development mode) - // close app - await electronApp.close(); -}); - -test("launch app2", async () => { - // Launch Electron app. - const electronApp = await electron.launch({ args: [getAppPath()] }); - - // Evaluation expression in the Electron context. - const appPath = await electronApp.evaluate(async ({ app }) => { - // This runs in the main Electron process, parameter here is always - // the result of the require('electron') in the main app script. - return app.getAppPath(); - }); - console.log(appPath); - - // Get the first window that the app opens, wait if necessary. - const window = await electronApp.firstWindow(); - // Print the title. - console.log(await window.title()); - // Capture a screenshot. - await window.screenshot({ path: "test-results/intro.png" }); - // Direct Electron console to Node terminal. - window.on("console", console.log); - //// Click button. - //await window.click('text=Click me'); - // Exit app. - await electronApp.close(); -}); - -test("save screenshot", async () => { - const electronApp = await electron.launch({ args: [getAppPath()] }); - const window = await electronApp.firstWindow(); - await window.screenshot({ path: "test-results/intro.png" }); - // close app - await electronApp.close(); -}); - -test("example test", async () => { - const electronApp = await electron.launch({ args: [getAppPath()] }); - const isPackaged = await electronApp.evaluate(async ({ app }) => { - // This runs in Electron's main process, parameter here is always - // the result of the require('electron') in the main app script. - return app.isPackaged; - }); - - expect(isPackaged).toBe(false); - - // Wait for the first BrowserWindow to open - // and return its Page object - const window = await electronApp.firstWindow(); - await window.screenshot({ path: "test-results/intro.png" }); - - // close app - await electronApp.close(); -}); From c0dc44f0748c9f57e1f7fdb0f6d2f80ce57f1a44 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 23 Jan 2025 21:25:37 -0800 Subject: [PATCH 012/138] tests can now run in parallel (based on each test file) --- ts/packages/shell/playwright.config.ts | 1 + ts/packages/shell/src/main/index.ts | 3 +- ts/packages/shell/test/configCommands.spec.ts | 52 ++--- ts/packages/shell/test/hostWindow.spec.ts | 188 +++++++++--------- ts/packages/shell/test/testHelper.ts | 20 +- 5 files changed, 140 insertions(+), 124 deletions(-) diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts index 1045a3141..7fb36ad8e 100644 --- a/ts/packages/shell/playwright.config.ts +++ b/ts/packages/shell/playwright.config.ts @@ -42,6 +42,7 @@ export default defineConfig({ { name: "chromium", use: { ...devices["Desktop Chrome"] }, + fullyParallel: false }, // { diff --git a/ts/packages/shell/src/main/index.ts b/ts/packages/shell/src/main/index.ts index d0e7f38be..b0515b6df 100644 --- a/ts/packages/shell/src/main/index.ts +++ b/ts/packages/shell/src/main/index.ts @@ -634,7 +634,8 @@ async function initialize() { // On windows, we will spin up a local end point that listens // for pen events which will trigger speech reco - if (process.platform == "win32") { + // Don't spin this up during testing + if (process.platform == "win32" && (process.env["INSTANCE_NAME"] == undefined || process.env["INSTANCE_NAME"].startsWith("test_") == false)) { const pipePath = path.join("\\\\.\\pipe\\TypeAgent", "speech"); const server = net.createServer((stream) => { stream.on("data", (c) => { diff --git a/ts/packages/shell/test/configCommands.spec.ts b/ts/packages/shell/test/configCommands.spec.ts index 2930777d7..44311b3ec 100644 --- a/ts/packages/shell/test/configCommands.spec.ts +++ b/ts/packages/shell/test/configCommands.spec.ts @@ -17,47 +17,53 @@ import { waitForAgentMessage, } from "./testHelper"; -test("@config dev", async () => { +// Annotate entire file as serial. +test.describe.configure({ mode: 'serial' }); - // launch the app - const mainWindow: Page = await testSetup(); +test.describe("@config Commands", () => { - let msg = await sendUserRequestAndWaitForResponse(`@config dev`, mainWindow); + test("@config dev", async () => { - expect(msg.toLowerCase(), "Dev mode was not turned on as expected.").toBe("development mode is enabled.") + // launch the app + const mainWindow: Page = await testSetup(); - msg = await sendUserRequestAndWaitForResponse(`@config dev on`, mainWindow); + let msg = await sendUserRequestAndWaitForResponse(`@config dev`, mainWindow); - expect(msg.toLowerCase(), "Dev mode was not turned on as expected.").toBe("development mode is enabled.") + expect(msg.toLowerCase(), "Dev mode was not turned on as expected.").toBe("development mode is enabled.") - msg = await sendUserRequestAndWaitForResponse(`@config dev off`, mainWindow); + msg = await sendUserRequestAndWaitForResponse(`@config dev on`, mainWindow); - expect(msg.toLowerCase(), "Dev mode was not turned off as expected.").toBe("development mode is disabled.") + expect(msg.toLowerCase(), "Dev mode was not turned on as expected.").toBe("development mode is enabled.") - // close the application - await exitApplication(mainWindow); -}); + msg = await sendUserRequestAndWaitForResponse(`@config dev off`, mainWindow); + + expect(msg.toLowerCase(), "Dev mode was not turned off as expected.").toBe("development mode is disabled.") + + // close the application + await exitApplication(mainWindow); + }); -test("@config schema", async () => { + test("@config schema", async () => { - // launch the app - const mainWindow: Page = await testSetup(); + // launch the app + const mainWindow: Page = await testSetup(); - let msg = await sendUserRequestAndWaitForResponse(`@config schema oracle`, mainWindow); + let msg = await sendUserRequestAndWaitForResponse(`@config schema oracle`, mainWindow); - expect(msg.toLowerCase(), "Oracle scheme should be ON but it is OFF.").toContain("✅"); + expect(msg.toLowerCase(), "Oracle scheme should be ON but it is OFF.").toContain("✅"); - msg = await sendUserRequestAndWaitForResponse(`@config schema --off oracle`, mainWindow); + msg = await sendUserRequestAndWaitForResponse(`@config schema --off oracle`, mainWindow); - expect(msg.toLowerCase(), "Oracle schema should be OFF but is is ON.").toContain("❌") + expect(msg.toLowerCase(), "Oracle schema should be OFF but is is ON.").toContain("❌") - msg = await sendUserRequestAndWaitForResponse(`@config dev off`, mainWindow); + msg = await sendUserRequestAndWaitForResponse(`@config dev off`, mainWindow); - expect(msg.toLowerCase(), "Dev mode was not turned off as expected.").toBe("development mode is disabled.") + expect(msg.toLowerCase(), "Dev mode was not turned off as expected.").toBe("development mode is disabled.") - // close the application - await exitApplication(mainWindow); + // close the application + await exitApplication(mainWindow); + }); }); // TODO: Test action correction \ No newline at end of file diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index ff0a571d3..c0417296b 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -19,112 +19,118 @@ import { } from "./testHelper"; import { exit } from "process"; -/** - * Test to ensure that the shell recall startup layout (position, size) - */ -test("remember window position", async () => { - let agentMessageCount = 0; - - const firstWindow = await testSetup(); - - // verify shell title - const title = await firstWindow.title(); - expect(title.indexOf("🤖") > -1, "Title expecting 🤖 but is missing."); - - // resize the shell by sending @shell settings set size "[width, height]" - const width: number = Math.ceil(Math.random() * 800 + 200); - const height: number = Math.ceil(Math.random() * 800 + 200); - await sendUserRequestAndWaitForResponse( - `@shell set size "[${width}, ${height}]"`, - firstWindow, - ); - - // move the window - const x: number = Math.ceil(Math.random() * 100); - const y: number = Math.ceil(Math.random() * 100); - - await sendUserRequestAndWaitForResponse(`@shell set position "[${x}, ${y}]"`, firstWindow); - - // close the application - await exitApplication(firstWindow); - - // restart the app - const newWindow: Page = await testSetup(); - - // get window size/position - const msg = await sendUserRequestAndWaitForResponse(`@shell show raw`, newWindow); - - // get the shell size and location from the raw settings - const lines: string[] = msg.split("\n"); - const newWidth: number = parseInt(lines[1].split(":")[1].trim()); - const newHeight: number = parseInt(lines[2].split(":")[1].trim()); - const newX: number = parseInt(lines[4].split(":")[1].trim()); - const newY: number = parseInt(lines[5].split(":")[1].trim()); - - expect(newHeight, `Window height mismatch! Expected ${height} got ${height}`).toBe(newHeight); - expect(newWidth, `Window width mismatch! Expected ${width} got ${width}`).toBe(newWidth); - expect(newX, `X position mismatch! Expected ${x} got ${newX}`).toBe(x); - expect(newY, `Y position mismatch!Expected ${y} got ${newY}`).toBe(y); - - // close the application - await exitApplication(newWindow); -}); +// Annotate entire file as serial. +test.describe.configure({ mode: 'serial' }); -/** - * Ensures zoom level is working - */ -test("zoom level", async () => { +test.describe("Shell interface tests", () => { - // start the app - const mainWindow = await testSetup(); + /** + * Test to ensure that the shell recall startup layout (position, size) + */ + test("remember window position", async () => { + let agentMessageCount = 0; - // test 80% zoom - await testZoomLevel(0.8, mainWindow); + const firstWindow = await testSetup(); - // set the zoom level to 120% - await testZoomLevel(1.2, mainWindow); + // verify shell title + const title = await firstWindow.title(); + expect(title.indexOf("🤖") > -1, "Title expecting 🤖 but is missing."); - // reset zoomLevel - await testZoomLevel(1, mainWindow); + // resize the shell by sending @shell settings set size "[width, height]" + const width: number = Math.ceil(Math.random() * 800 + 200); + const height: number = Math.ceil(Math.random() * 800 + 200); + await sendUserRequestAndWaitForResponse( + `@shell set size "[${width}, ${height}]"`, + firstWindow, + ); - // close the application - await exitApplication(mainWindow); -}); + // move the window + const x: number = Math.ceil(Math.random() * 100); + const y: number = Math.ceil(Math.random() * 100); + + await sendUserRequestAndWaitForResponse(`@shell set position "[${x}, ${y}]"`, firstWindow); + + // close the application + await exitApplication(firstWindow); + + // restart the app + const newWindow: Page = await testSetup(); + + // get window size/position + const msg = await sendUserRequestAndWaitForResponse(`@shell show raw`, newWindow); + + // get the shell size and location from the raw settings + const lines: string[] = msg.split("\n"); + const newWidth: number = parseInt(lines[1].split(":")[1].trim()); + const newHeight: number = parseInt(lines[2].split(":")[1].trim()); + const newX: number = parseInt(lines[4].split(":")[1].trim()); + const newY: number = parseInt(lines[5].split(":")[1].trim()); + + expect(newHeight, `Window height mismatch! Expected ${height} got ${height}`).toBe(newHeight); + expect(newWidth, `Window width mismatch! Expected ${width} got ${width}`).toBe(newWidth); + expect(newX, `X position mismatch! Expected ${x} got ${newX}`).toBe(x); + expect(newY, `Y position mismatch!Expected ${y} got ${newY}`).toBe(y); + + // close the application + await exitApplication(newWindow); + }); + + /** + * Ensures zoom level is working + */ + test("zoom level", async () => { + + // start the app + const mainWindow = await testSetup(); + + // test 80% zoom + await testZoomLevel(0.8, mainWindow); + + // set the zoom level to 120% + await testZoomLevel(1.2, mainWindow); + + // reset zoomLevel + await testZoomLevel(1, mainWindow); + + // close the application + await exitApplication(mainWindow); + }); -async function testZoomLevel(level: number, page: Page) { + async function testZoomLevel(level: number, page: Page) { - // set the zoom level to 80% - await sendUserRequestAndWaitForResponse(`@shell set zoomLevel ${level}`, page); + // set the zoom level to 80% + await sendUserRequestAndWaitForResponse(`@shell set zoomLevel ${level}`, page); - // get the title - let title = await page.title(); + // get the title + let title = await page.title(); - // get zoom level out of title - let subTitle: string = title.match(/\d+%/)![0]; - let zoomLevel: number = parseInt(subTitle.substring(0, subTitle.length - 1)); + // get zoom level out of title + let subTitle: string = title.match(/\d+%/)![0]; + let zoomLevel: number = parseInt(subTitle.substring(0, subTitle.length - 1)); - expect(zoomLevel, `Unexpected zoomLevel, expected ${level * 100}, got ${zoomLevel}`).toBe(level * 100); -} + expect(zoomLevel, `Unexpected zoomLevel, expected ${level * 100}, got ${zoomLevel}`).toBe(level * 100); + } -/** - * Ensure send button is behaving - */ -test("send button state", async () => { - let agentMessageCount = 0; + /** + * Ensure send button is behaving + */ + test("send button state", async () => { + let agentMessageCount = 0; - // start the app - const mainWindow = await testSetup(); + // start the app + const mainWindow = await testSetup(); - // make sure send button is disabled - const sendButton = await mainWindow.locator("#sendbutton"); - await expect(sendButton, "Send button expected to be disabled.").toBeDisabled(); + // make sure send button is disabled + const sendButton = await mainWindow.locator("#sendbutton"); + await expect(sendButton, "Send button expected to be disabled.").toBeDisabled(); - // put some text in the text box - const element = await mainWindow.waitForSelector("#phraseDiv"); - await element.fill("This is a test..."); + // put some text in the text box + const element = await mainWindow.waitForSelector("#phraseDiv"); + await element.fill("This is a test..."); - await expect(sendButton, "Send button expected to be enabled.").toBeEnabled(); + await expect(sendButton, "Send button expected to be enabled.").toBeEnabled(); - // close the application - await exitApplication(mainWindow); + // close the application + await exitApplication(mainWindow); + }); }); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index ff2d721d1..f4d467539 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -10,27 +10,28 @@ import { import fs from "node:fs"; import path from "node:path"; -let runningApplication: ElectronApplication | null = null; +const runningApplication: Map = new Map(); /** * Starts the electron app and returns the main page after the greeting agent message has been posted. */ export async function testSetup(): Promise { - // agent message count - let agentMessageCount = 0; + // this is needed to isolate these tests session from other concurrently running tests + process.env["INSTANCE_NAME"] = `test_${process.env["TEST_WORKER_INDEX"]}`; + // start the app - if (runningApplication !== null) { + if (runningApplication.has(process.env["TEST_WORKER_INDEX"]!)) { throw new Error("Application instance already running. Did you shutdown cleanly?"); } - runningApplication = await electron.launch({ args: [getAppPath()] }); + runningApplication.set(process.env["TEST_WORKER_INDEX"]!, await electron.launch({ args: [getAppPath()] })); // get the main window - const mainWindow: Page = await runningApplication.firstWindow(); + const mainWindow: Page = await runningApplication.get(process.env["TEST_WORKER_INDEX"]!)!.firstWindow(); // wait for agent greeting - await waitForAgentMessage(mainWindow, 10000, ++agentMessageCount); + await waitForAgentMessage(mainWindow, 10000, 1); return mainWindow; } @@ -42,8 +43,9 @@ export async function testSetup(): Promise { export async function exitApplication(page: Page): Promise { await sendUserRequestFast("@exit", page); - await runningApplication!.close(); - runningApplication = null; + await runningApplication.get(process.env["TEST_WORKER_INDEX"]!)!.close(); + + runningApplication.delete(process.env["TEST_WORKER_INDEX"]!); } /** From f476d6b94cb4eec731b51078d57d5c1fadc0813f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 23 Jan 2025 22:15:56 -0800 Subject: [PATCH 013/138] added session commands (unverified) --- .../shell/test/sessionCommands.spec.ts | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 ts/packages/shell/test/sessionCommands.spec.ts diff --git a/ts/packages/shell/test/sessionCommands.spec.ts b/ts/packages/shell/test/sessionCommands.spec.ts new file mode 100644 index 000000000..2518d845e --- /dev/null +++ b/ts/packages/shell/test/sessionCommands.spec.ts @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import test, { + _electron, + _electron as electron, + expect, + Page, +} from "@playwright/test"; +import { + exitApplication, + getAppPath, + getLastAgentMessage, + sendUserRequest, + sendUserRequestAndWaitForResponse, + testSetup, + waitForAgentMessage, +} from "./testHelper"; + +// Annotate entire file as serial. +test.describe.configure({ mode: 'serial' }); + +test.describe("@session Commands", () => { + + test("@session new/list", async () => { + + // launch the app + const mainWindow: Page = await testSetup(); + + // get the session count + let msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); + + const sessions: string[] = msg.split("\n"); + + msg = await sendUserRequestAndWaitForResponse(`@session new`, mainWindow); + expect(msg.toLowerCase()).toContain("New session created: "); + + msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); + const newSessions: string[] = msg.split("\n"); + + expect(newSessions.length, "Session count mismatch!").toBe(sessions.length + 1); + + msg = await sendUserRequestAndWaitForResponse(`@history`, mainWindow); + expect(msg.length, "History NOT cleared!").toBe(0); + + // close the application + await exitApplication(mainWindow); + }); +}); + +// TODO: Test action correction \ No newline at end of file From a45e338ed29c661a194533ccee5977615da4ca2a Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 24 Jan 2025 10:59:19 -0800 Subject: [PATCH 014/138] added global test teardown and setup --- ts/packages/shell/playwright.config.ts | 41 ++++-------- ts/packages/shell/test/global.setup.ts | 10 +++ ts/packages/shell/test/global.teardown.ts | 10 +++ .../shell/test/sessionCommands.spec.ts | 2 +- ts/packages/shell/test/testHelper.ts | 64 ++++++++++++++----- 5 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 ts/packages/shell/test/global.setup.ts create mode 100644 ts/packages/shell/test/global.teardown.ts diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts index 7fb36ad8e..a44f158d2 100644 --- a/ts/packages/shell/playwright.config.ts +++ b/ts/packages/shell/playwright.config.ts @@ -39,43 +39,24 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ + { + name: `global setup`, + testMatch: /global\.setup\.ts/, + }, { name: "chromium", use: { ...devices["Desktop Chrome"] }, fullyParallel: false }, - - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, + { + name: `global teardown`, + testMatch: /global\.teardown\.ts/, + }, ], + //// path to the global teardown files. + //globalTeardown: require.resolve('./test/global-teardown'), + /* Run your local dev server before starting the tests */ // webServer: { // command: 'npm run start', diff --git a/ts/packages/shell/test/global.setup.ts b/ts/packages/shell/test/global.setup.ts new file mode 100644 index 000000000..82b17ad6f --- /dev/null +++ b/ts/packages/shell/test/global.setup.ts @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { test as setup } from '@playwright/test'; +import { deleteTestProfiles } from './testHelper'; + +// clear up old test profile data +setup('clear old test data', async ({ }) => { + deleteTestProfiles(); +}); \ No newline at end of file diff --git a/ts/packages/shell/test/global.teardown.ts b/ts/packages/shell/test/global.teardown.ts new file mode 100644 index 000000000..ff66ffe9a --- /dev/null +++ b/ts/packages/shell/test/global.teardown.ts @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { test as teardown } from '@playwright/test'; +import { deleteTestProfiles } from './testHelper'; + +// clear up old test profile data +teardown('clear test data', async ({ }) => { + deleteTestProfiles(); +}); \ No newline at end of file diff --git a/ts/packages/shell/test/sessionCommands.spec.ts b/ts/packages/shell/test/sessionCommands.spec.ts index 2518d845e..05801d6b3 100644 --- a/ts/packages/shell/test/sessionCommands.spec.ts +++ b/ts/packages/shell/test/sessionCommands.spec.ts @@ -33,7 +33,7 @@ test.describe("@session Commands", () => { const sessions: string[] = msg.split("\n"); msg = await sendUserRequestAndWaitForResponse(`@session new`, mainWindow); - expect(msg.toLowerCase()).toContain("New session created: "); + expect(msg.toLowerCase()).toContain("new session created: "); msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); const newSessions: string[] = msg.split("\n"); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index f4d467539..a708fa650 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -7,10 +7,12 @@ import { ElectronApplication, Locator, Page } from "@playwright/test"; +import { profile } from "node:console"; import fs from "node:fs"; import path from "node:path"; +import os from "node:os"; -const runningApplication: Map = new Map(); +const runningApplications: Map = new Map(); /** * Starts the electron app and returns the main page after the greeting agent message has been posted. @@ -18,22 +20,37 @@ const runningApplication: Map = new Map { // this is needed to isolate these tests session from other concurrently running tests - process.env["INSTANCE_NAME"] = `test_${process.env["TEST_WORKER_INDEX"]}`; - - // start the app - if (runningApplication.has(process.env["TEST_WORKER_INDEX"]!)) { - throw new Error("Application instance already running. Did you shutdown cleanly?"); - } + process.env["INSTANCE_NAME"] = `test_${process.env["TEST_WORKER_INDEX"]}_${process.env["TEST_PARALLEL_INDEX"]}`; + + // we may have to retry restarting the application due to session file locks or other such startup failures + let retryAttempt = 0; + + do { + try { + if (runningApplications.has(process.env["INSTANCE_NAME"]!)) { + throw new Error("Application instance already running. Did you shutdown cleanly?"); + } + + // start the app + runningApplications.set(process.env["INSTANCE_NAME"]!, await electron.launch({ args: [getAppPath()] })); - runningApplication.set(process.env["TEST_WORKER_INDEX"]!, await electron.launch({ args: [getAppPath()] })); + // get the main window + const mainWindow: Page = await runningApplications.get(process.env["INSTANCE_NAME"]!)!.firstWindow(); - // get the main window - const mainWindow: Page = await runningApplication.get(process.env["TEST_WORKER_INDEX"]!)!.firstWindow(); + // wait for agent greeting + await waitForAgentMessage(mainWindow, 10000, 1); - // wait for agent greeting - await waitForAgentMessage(mainWindow, 10000, 1); + return mainWindow; - return mainWindow; + } catch (e) { + console.warn(`Unable to start electrom application (${process.env["INSTANCE_NAME"]}). Attempt ${retryAttempt} of 5`); + retryAttempt++; + + runningApplications.delete(process.env["INSTANCE_NAME"]!); + } + } while (retryAttempt <= 5); + + throw new Error("Failed to start electrom app after 5 attemps."); } /** @@ -43,9 +60,9 @@ export async function testSetup(): Promise { export async function exitApplication(page: Page): Promise { await sendUserRequestFast("@exit", page); - await runningApplication.get(process.env["TEST_WORKER_INDEX"]!)!.close(); + await runningApplications.get(process.env["INSTANCE_NAME"]!)!.close(); - runningApplication.delete(process.env["TEST_WORKER_INDEX"]!); + runningApplications.delete(process.env["INSTANCE_NAME"]!); } /** @@ -137,3 +154,20 @@ export async function waitForAgentMessage(page: Page, timeout: number, expectedM } while (timeWaited <= timeout && messageCount == originalAgentMessageCount); } + +export function deleteTestProfiles() { + const profileDir = path.join(os.homedir(), ".typeagent", "profiles"); + + if (fs.existsSync(profileDir)) { + fs.readdirSync(profileDir).map((dirEnt) =>{ + if (dirEnt.startsWith("test_")) { + const dir: string = path.join(profileDir, dirEnt) + try { + fs.rmSync(dir, { recursive: true, force: true }); + } catch (e) { + console.warn(`Unable to delete '${dir}', ${e}`); + } + } + }); + } +} From 8d69640c397a101d1203856d3fc93b2013f9485b Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 24 Jan 2025 12:50:15 -0800 Subject: [PATCH 015/138] use locator instead of element lookup --- ts/packages/shell/test/hostWindow.spec.ts | 1 - ts/packages/shell/test/testHelper.ts | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index c0417296b..8c7538b0e 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -79,7 +79,6 @@ test.describe("Shell interface tests", () => { * Ensures zoom level is working */ test("zoom level", async () => { - // start the app const mainWindow = await testSetup(); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index a708fa650..1f315ac64 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -82,11 +82,12 @@ export function getAppPath(): string { * @param prompt The user request/prompt. * @param page The maing page from the electron host application. */ -export async function sendUserRequest(prompt: string, page: Page) { - const element = await page.waitForSelector("#phraseDiv"); - await element.focus(); - await element.fill(prompt); - await element.press("Enter"); +export async function sendUserRequest(prompt: string, page: Page) { + const locator: Locator = page.locator("#phraseDiv"); + await locator.waitFor({ timeout: 10000, state: "attached" }); + await locator.focus(); + await locator.fill(prompt); + await locator.press("Enter"); } /** From 552614be9b555f79af7da45489c4c6c7ddaa4c6e Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 24 Jan 2025 15:15:08 -0800 Subject: [PATCH 016/138] more test updates --- ts/packages/coda/src/webSocket.ts | 2 +- ts/packages/shell/playwright.config.ts | 3 --- ts/packages/shell/src/main/agent.ts | 4 +++- ts/packages/shell/src/main/index.ts | 8 +++++--- ts/packages/shell/test/hostWindow.spec.ts | 2 +- ts/packages/shell/test/testHelper.ts | 17 ++++++++++++++--- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/ts/packages/coda/src/webSocket.ts b/ts/packages/coda/src/webSocket.ts index a3f4fb9d0..ffb8c6f0d 100644 --- a/ts/packages/coda/src/webSocket.ts +++ b/ts/packages/coda/src/webSocket.ts @@ -17,7 +17,7 @@ export async function createWebSocket( clientId?: string, ) { return new Promise((resolve, reject) => { - let endpoint = "ws://localhost:8080"; + let endpoint = process.env["WEBSOCKET_HOST"] ?? "ws://localhost:8080"; endpoint += `?channel=${channel}&role=${role}`; if (clientId) { endpoint += `clientId=${clientId}`; diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts index a44f158d2..9cfa0188d 100644 --- a/ts/packages/shell/playwright.config.ts +++ b/ts/packages/shell/playwright.config.ts @@ -54,9 +54,6 @@ export default defineConfig({ }, ], - //// path to the global teardown files. - //globalTeardown: require.resolve('./test/global-teardown'), - /* Run your local dev server before starting the tests */ // webServer: { // command: 'npm run start', diff --git a/ts/packages/shell/src/main/agent.ts b/ts/packages/shell/src/main/agent.ts index b5e02823e..9cf96c8a2 100644 --- a/ts/packages/shell/src/main/agent.ts +++ b/ts/packages/shell/src/main/agent.ts @@ -21,6 +21,8 @@ import { } from "@typeagent/agent-sdk/helpers/display"; import { getLocalWhisperCommandHandlers } from "./localWhisperCommandHandler.js"; +const port = process.env.PORT || 9001; + type ShellContext = { settings: ShellSettings; }; @@ -206,7 +208,7 @@ class ShellOpenWebContentView implements CommandHandler { break; case "markdown": - targetUrl = new URL("http://localhost:9001/"); + targetUrl = new URL(`http://localhost:${port}/`); break; default: diff --git a/ts/packages/shell/src/main/index.ts b/ts/packages/shell/src/main/index.ts index b0515b6df..a3b921863 100644 --- a/ts/packages/shell/src/main/index.ts +++ b/ts/packages/shell/src/main/index.ts @@ -552,10 +552,11 @@ async function initialize() { }); createDispatcherRpcServer(dispatcher, dispatcherChannel.channel); - if (ShellSettings.getinstance().agentGreeting) { - processShellRequest("@greeting", "agent-0", []); - } ipcMain.on("dom ready", async () => { + if (ShellSettings.getinstance().agentGreeting) { + processShellRequest("@greeting", "agent-0", []); + } + settingSummary = dispatcher.getSettingSummary(); chatView?.webContents.send( "setting-summary-changed", @@ -576,6 +577,7 @@ async function initialize() { require("electron").shell.openExternal(details.url); return { action: "deny" }; }); + }); await initializeSpeech(); diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index 8c7538b0e..c6eb13d6c 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -27,7 +27,7 @@ test.describe("Shell interface tests", () => { /** * Test to ensure that the shell recall startup layout (position, size) */ - test("remember window position", async () => { + test.skip("remember window position", async () => { let agentMessageCount = 0; const firstWindow = await testSetup(); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 1f315ac64..ffa903c03 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -22,6 +22,10 @@ export async function testSetup(): Promise { // this is needed to isolate these tests session from other concurrently running tests process.env["INSTANCE_NAME"] = `test_${process.env["TEST_WORKER_INDEX"]}_${process.env["TEST_PARALLEL_INDEX"]}`; + // other related multi-instance varibles that need to be modfied to ensure we can run multiple shell instances + process.env["PORT"] = (9001 + parseInt(process.env["TEST_WORKER_INDEX"]!)).toString(); + process.env["WEBSOCKET_HOST"] = (8080 + parseInt(process.env["TEST_WORKER_INDEX"]!)).toString(); + // we may have to retry restarting the application due to session file locks or other such startup failures let retryAttempt = 0; @@ -32,10 +36,13 @@ export async function testSetup(): Promise { } // start the app - runningApplications.set(process.env["INSTANCE_NAME"]!, await electron.launch({ args: [getAppPath()] })); + console.log(`Starting ${process.env["INSTANCE_NAME"]}`); + const app: ElectronApplication = await electron.launch({ args: [getAppPath()] }); + runningApplications.set(process.env["INSTANCE_NAME"]!, app); // get the main window - const mainWindow: Page = await runningApplications.get(process.env["INSTANCE_NAME"]!)!.firstWindow(); + const mainWindow: Page = await app.firstWindow(); + await mainWindow.waitForLoadState("domcontentloaded"); // wait for agent greeting await waitForAgentMessage(mainWindow, 10000, 1); @@ -43,9 +50,13 @@ export async function testSetup(): Promise { return mainWindow; } catch (e) { - console.warn(`Unable to start electrom application (${process.env["INSTANCE_NAME"]}). Attempt ${retryAttempt} of 5`); + console.warn(`Unable to start electrom application (${process.env["INSTANCE_NAME"]}). Attempt ${retryAttempt} of 5`); retryAttempt++; + if (runningApplications.get(process.env["INSTANCE_NAME"]) !== null) { + runningApplications.get(process.env["INSTANCE_NAME"]!)!.close(); + } + runningApplications.delete(process.env["INSTANCE_NAME"]!); } } while (retryAttempt <= 5); From 940da447c5d574e96145e9f1cfbe2a5a2ca133d1 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 24 Jan 2025 16:11:01 -0800 Subject: [PATCH 017/138] moved shell settings file to instance directory root folder. added env command to show singular env var --- .../system/handlers/envCommandHandler.ts | 35 +++++++++++++++++-- .../src/context/system/systemAgent.ts | 4 +-- ts/packages/dispatcher/src/index.ts | 2 +- ts/packages/shell/src/main/shellSettings.ts | 6 ++-- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/ts/packages/dispatcher/src/context/system/handlers/envCommandHandler.ts b/ts/packages/dispatcher/src/context/system/handlers/envCommandHandler.ts index 8da0bd224..30ac11a5d 100644 --- a/ts/packages/dispatcher/src/context/system/handlers/envCommandHandler.ts +++ b/ts/packages/dispatcher/src/context/system/handlers/envCommandHandler.ts @@ -2,13 +2,15 @@ // Licensed under the MIT License. import { + CommandHandler, CommandHandlerNoParams, CommandHandlerTable, } from "@typeagent/agent-sdk/helpers/command"; import { CommandHandlerContext } from "../../commandHandlerContext.js"; -import { ActionContext } from "@typeagent/agent-sdk"; -import { displayResult } from "@typeagent/agent-sdk/helpers/display"; +import { ActionContext, ParsedCommandParams } from "@typeagent/agent-sdk"; +import { displayError, displayResult } from "@typeagent/agent-sdk/helpers/display"; import dotenv from "dotenv"; +import { Action } from "agent-cache"; export class EnvCommandHandler implements CommandHandlerNoParams { public readonly description = @@ -38,3 +40,32 @@ export class EnvCommandHandler implements CommandHandlerNoParams { displayResult(table, context); } } + +export class EnvVarCommandHandler implements CommandHandler { + public readonly description: string = "Echos the value of a named environment variable to the user interface"; + public readonly parameters = { + args: { + name: { + description: "The name of the environment variable.", + }, + }, + } as const; + public async run(context: ActionContext, params: ParsedCommandParams,) { + if (process.env[params.args.name]) { + displayResult(process.env[params.args.name]!, context); + } else { + displayError(`The environment variable ${params.args.name} does not exist.`, context); + } + } +} + +export function getEnvCommandHandlers(): CommandHandlerTable { + return { + description: "Environment variable commands", + defaultSubCommand: "all", + commands: { + all: new EnvCommandHandler(), + get: new EnvVarCommandHandler() + } + }; +} diff --git a/ts/packages/dispatcher/src/context/system/systemAgent.ts b/ts/packages/dispatcher/src/context/system/systemAgent.ts index 59a794624..274a12c35 100644 --- a/ts/packages/dispatcher/src/context/system/systemAgent.ts +++ b/ts/packages/dispatcher/src/context/system/systemAgent.ts @@ -61,7 +61,7 @@ import { getParameterNames, validateAction, } from "action-schema"; -import { EnvCommandHandler } from "./handlers/envCommandHandler.js"; +import { EnvCommandHandler, getEnvCommandHandlers } from "./handlers/envCommandHandler.js"; import { executeNotificationAction } from "./action/notificationActionHandler.js"; import { executeHistoryAction } from "./action/historyActionHandler.js"; @@ -357,7 +357,7 @@ const systemHandlers: CommandHandlerTable = { random: getRandomCommandHandlers(), notify: getNotifyCommandHandlers(), token: getTokenCommandHandlers(), - env: new EnvCommandHandler(), + env: getEnvCommandHandlers(), }, }; diff --git a/ts/packages/dispatcher/src/index.ts b/ts/packages/dispatcher/src/index.ts index 0aa2f31e1..8a5aa29a9 100644 --- a/ts/packages/dispatcher/src/index.ts +++ b/ts/packages/dispatcher/src/index.ts @@ -15,4 +15,4 @@ export type { TemplateEditConfig, TemplateData, } from "./translation/actionTemplate.js"; -export { getUserDataDir } from "./utils/userData.js"; +export { getUserDataDir, getInstanceDir } from "./utils/userData.js"; diff --git a/ts/packages/shell/src/main/shellSettings.ts b/ts/packages/shell/src/main/shellSettings.ts index 4959b8a76..e50373c7f 100644 --- a/ts/packages/shell/src/main/shellSettings.ts +++ b/ts/packages/shell/src/main/shellSettings.ts @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { app } from "electron"; import registerDebug from "debug"; import { readFileSync, existsSync, writeFileSync } from "fs"; -import path from "path"; import { defaultSettings, ShellSettingsType, @@ -15,6 +13,8 @@ import { DisplayType, EmptyFunction, } from "../preload/electronTypes.js"; +import { getInstanceDir } from "agent-dispatcher"; +import path from "path"; const debugShell = registerDebug("typeagent:shell"); @@ -94,7 +94,7 @@ export class ShellSettings } public static get filePath(): string { - return path.join(app.getPath("userData"), "shellSettings.json"); + return path.join(getInstanceDir(), "shellSettings.json"); } public static getinstance = (): ShellSettings => { From 792d8685020199bc227ca7de82c9ff79a15b6ed5 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 24 Jan 2025 18:28:49 -0800 Subject: [PATCH 018/138] tests intermittently passing --- ts/packages/shell/playwright.config.ts | 17 +++++++++------ ts/packages/shell/src/main/shellSettings.ts | 1 + ts/packages/shell/test/hostWindow.spec.ts | 3 +-- ts/packages/shell/test/simple.spec.ts | 13 +++++++++++ ts/packages/shell/test/testHelper.ts | 24 +++++++++++---------- 5 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 ts/packages/shell/test/simple.spec.ts diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts index 9cfa0188d..e85c893ca 100644 --- a/ts/packages/shell/playwright.config.ts +++ b/ts/packages/shell/playwright.config.ts @@ -23,7 +23,8 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + workers: 1, // fails with timeouts with more than 2 workers. :( + //process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: "html", /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ @@ -35,22 +36,24 @@ export default defineConfig({ trace: "on-first-retry", }, - timeout: 120_000, // Set global timeout to 120 seconds + timeout: 300_000, // Set global timeout to 120 seconds /* Configure projects for major browsers */ projects: [ { name: `global setup`, testMatch: /global\.setup\.ts/, - }, - { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, - fullyParallel: false + teardown: "global teardown", }, { name: `global teardown`, testMatch: /global\.teardown\.ts/, + }, + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + fullyParallel: false, + dependencies: [ "global setup"], }, ], diff --git a/ts/packages/shell/src/main/shellSettings.ts b/ts/packages/shell/src/main/shellSettings.ts index e50373c7f..83024a9db 100644 --- a/ts/packages/shell/src/main/shellSettings.ts +++ b/ts/packages/shell/src/main/shellSettings.ts @@ -152,6 +152,7 @@ export class ShellSettings value.toLowerCase() === "true" || value === "1"; break; case "object": + case "undefined": ShellSettings.getinstance()[name] = JSON.parse(value); break; } diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index c6eb13d6c..bdf7bad3e 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -27,7 +27,7 @@ test.describe("Shell interface tests", () => { /** * Test to ensure that the shell recall startup layout (position, size) */ - test.skip("remember window position", async () => { + test("remember window position", async () => { let agentMessageCount = 0; const firstWindow = await testSetup(); @@ -96,7 +96,6 @@ test.describe("Shell interface tests", () => { }); async function testZoomLevel(level: number, page: Page) { - // set the zoom level to 80% await sendUserRequestAndWaitForResponse(`@shell set zoomLevel ${level}`, page); diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts new file mode 100644 index 000000000..483936cf3 --- /dev/null +++ b/ts/packages/shell/test/simple.spec.ts @@ -0,0 +1,13 @@ +import test, { ElectronApplication,_electron, + _electron as electron, } from "@playwright/test"; +import { getAppPath, testSetup } from "./testHelper"; + +test("dummy", async () => { + // do nothing +}); + +test("simple", async () => { + const window = await testSetup(); + await window.waitForTimeout(3000); + await window.close(); +}); \ No newline at end of file diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index ffa903c03..649c9fc41 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -24,10 +24,11 @@ export async function testSetup(): Promise { // other related multi-instance varibles that need to be modfied to ensure we can run multiple shell instances process.env["PORT"] = (9001 + parseInt(process.env["TEST_WORKER_INDEX"]!)).toString(); - process.env["WEBSOCKET_HOST"] = (8080 + parseInt(process.env["TEST_WORKER_INDEX"]!)).toString(); + process.env["WEBSOCKET_HOST"] = `ws://localhost:${(8080 + parseInt(process.env["TEST_WORKER_INDEX"]!))}`; // we may have to retry restarting the application due to session file locks or other such startup failures let retryAttempt = 0; + const maxRetries = 10; do { try { @@ -50,18 +51,18 @@ export async function testSetup(): Promise { return mainWindow; } catch (e) { - console.warn(`Unable to start electrom application (${process.env["INSTANCE_NAME"]}). Attempt ${retryAttempt} of 5`); + console.warn(`Unable to start electrom application (${process.env["INSTANCE_NAME"]}). Attempt ${retryAttempt} of ${maxRetries}`); retryAttempt++; - if (runningApplications.get(process.env["INSTANCE_NAME"]) !== null) { - runningApplications.get(process.env["INSTANCE_NAME"]!)!.close(); + if (runningApplications.get(process.env["INSTANCE_NAME"])) { + await runningApplications.get(process.env["INSTANCE_NAME"]!)!.close(); } runningApplications.delete(process.env["INSTANCE_NAME"]!); } - } while (retryAttempt <= 5); + } while (retryAttempt <= maxRetries); - throw new Error("Failed to start electrom app after 5 attemps."); + throw new Error(`Failed to start electrom app after ${maxRetries} attemps.`); } /** @@ -93,9 +94,9 @@ export function getAppPath(): string { * @param prompt The user request/prompt. * @param page The maing page from the electron host application. */ -export async function sendUserRequest(prompt: string, page: Page) { - const locator: Locator = page.locator("#phraseDiv"); - await locator.waitFor({ timeout: 10000, state: "attached" }); +export async function sendUserRequest(prompt: string, page: Page) { + const locator: Locator = await page.locator("#phraseDiv"); + await locator.waitFor({ timeout: 120000, state: "visible" }); await locator.focus(); await locator.fill(prompt); await locator.press("Enter"); @@ -107,8 +108,9 @@ export async function sendUserRequest(prompt: string, page: Page) { * @param page The maing page from the electron host application. */ export async function sendUserRequestFast(prompt: string, page: Page) { - const element = await page.waitForSelector("#phraseDiv"); - await element.fill(prompt); + const locator: Locator = await page.locator("#phraseDiv"); + await locator.waitFor({ timeout: 120000, state: "visible" }); + await locator.fill(prompt); page.keyboard.down("Enter"); } From db3f6f86e0c15073aead443d35a0147a01f163f4 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 24 Jan 2025 23:35:22 -0800 Subject: [PATCH 019/138] updating wokflow --- .../shell/.github/workflows/playwright.yml | 106 ++++++++++++++---- 1 file changed, 84 insertions(+), 22 deletions(-) diff --git a/ts/packages/shell/.github/workflows/playwright.yml b/ts/packages/shell/.github/workflows/playwright.yml index 07f2ae15f..1bc9092f2 100644 --- a/ts/packages/shell/.github/workflows/playwright.yml +++ b/ts/packages/shell/.github/workflows/playwright.yml @@ -1,30 +1,92 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -name: Playwright Tests +name: Shell Tests on: push: branches: [ dev/robgruen/electron_tests ] - pull_request: - branches: [ dev/robgruen/electron_tests ] + +# run only one of these at a time +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +permissions: + pull-requests: read + contents: read + jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest + build_ts: + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + version: [18, 20, 22] + exclude: + # TODO: Test timeout jest work for some reason + - os: windows-latest + version: 22 + + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Install dependencies - run: npm install -g pnpm && pnpm install - - name: Install Playwright Browsers - run: pnpm exec playwright install --with-deps - - name: Run Playwright tests - run: pnpm exec playwright test - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 + - if: runner.os == 'Linux' + run: | + sudo apt install libsecret-1-0 + - name: Setup Git LF + run: | + git config --global core.autocrlf false + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + ts: + - "ts/**" + - ".github/workflows/build-ts.yml" + - uses: pnpm/action-setup@v4 + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + name: Install pnpm + with: + package_json_file: ts/package.json + - uses: actions/setup-node@v4 + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + with: + node-version: ${{ matrix.version }} + cache: "pnpm" + cache-dependency-path: ts/pnpm-lock.yaml + - name: Install dependencies + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + working-directory: ts + run: | + pnpm install --frozen-lockfile --strict-peer-dependencies + - name: Build + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + working-directory: ts + run: | + npm run build + - name: Test + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + working-directory: ts + run: | + npm run test + + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Shell Tests + timeout-minutes: 180 + run: | + pnpm exec playwright test + working-directory: ./packages/shell + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 + + + - name: Lint + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + working-directory: ts + run: | + npm run lint From e50231b01585ade43d1c1b7b3ef8aaf89d5cb3ce Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 24 Jan 2025 23:37:39 -0800 Subject: [PATCH 020/138] moved file --- {ts/packages/shell/.github => .github}/workflows/playwright.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {ts/packages/shell/.github => .github}/workflows/playwright.yml (100%) diff --git a/ts/packages/shell/.github/workflows/playwright.yml b/.github/workflows/playwright.yml similarity index 100% rename from ts/packages/shell/.github/workflows/playwright.yml rename to .github/workflows/playwright.yml From fe4c9b85ba6c8b81f39d1b69b48a5a1ab2ec4877 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 24 Jan 2025 23:45:27 -0800 Subject: [PATCH 021/138] updated workflow --- .../shell/.github/workflows/playwright.yml | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 ts/packages/shell/.github/workflows/playwright.yml diff --git a/ts/packages/shell/.github/workflows/playwright.yml b/ts/packages/shell/.github/workflows/playwright.yml new file mode 100644 index 000000000..eaf8b78fa --- /dev/null +++ b/ts/packages/shell/.github/workflows/playwright.yml @@ -0,0 +1,92 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +name: Shell Tests +on: + push: + branches: [ dev/robgruen/electron_tests ] + +# run only one of these at a time +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +permissions: + pull-requests: read + contents: read + +jobs: + build_ts: + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + version: [18, 20, 22, 24] + exclude: + # TODO: Test timeout jest work for some reason + - os: windows-latest + version: 22 + + runs-on: ${{ matrix.os }} + steps: + - if: runner.os == 'Linux' + run: | + sudo apt install libsecret-1-0 + - name: Setup Git LF + run: | + git config --global core.autocrlf false + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + ts: + - "ts/**" + - ".github/workflows/build-ts.yml" + - uses: pnpm/action-setup@v4 + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + name: Install pnpm + with: + package_json_file: ts/package.json + - uses: actions/setup-node@v4 + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + with: + node-version: ${{ matrix.version }} + cache: "pnpm" + cache-dependency-path: ts/pnpm-lock.yaml + - name: Install dependencies + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + working-directory: ts + run: | + pnpm install --frozen-lockfile --strict-peer-dependencies + - name: Build + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + working-directory: ts + run: | + npm run build + # - name: Test + # if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + # working-directory: ts + # run: | + # npm run test + + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Shell Tests + timeout-minutes: 180 + run: | + pnpm exec playwright test + working-directory: ts/packages/shell + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 + + + - name: Lint + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + working-directory: ts + run: | + npm run lint From 13d8faebfd01548359b0eac7fe4465ece252bd65 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 24 Jan 2025 23:49:53 -0800 Subject: [PATCH 022/138] updates --- .github/workflows/playwright.yml | 14 +-- .../shell/.github/workflows/playwright.yml | 92 ------------------- 2 files changed, 7 insertions(+), 99 deletions(-) delete mode 100644 ts/packages/shell/.github/workflows/playwright.yml diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 1bc9092f2..eaf8b78fa 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -21,7 +21,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] - version: [18, 20, 22] + version: [18, 20, 22, 24] exclude: # TODO: Test timeout jest work for some reason - os: windows-latest @@ -64,11 +64,11 @@ jobs: working-directory: ts run: | npm run build - - name: Test - if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} - working-directory: ts - run: | - npm run test + # - name: Test + # if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + # working-directory: ts + # run: | + # npm run test - name: Install Playwright Browsers run: pnpm exec playwright install --with-deps @@ -76,7 +76,7 @@ jobs: timeout-minutes: 180 run: | pnpm exec playwright test - working-directory: ./packages/shell + working-directory: ts/packages/shell - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: diff --git a/ts/packages/shell/.github/workflows/playwright.yml b/ts/packages/shell/.github/workflows/playwright.yml deleted file mode 100644 index eaf8b78fa..000000000 --- a/ts/packages/shell/.github/workflows/playwright.yml +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -name: Shell Tests -on: - push: - branches: [ dev/robgruen/electron_tests ] - -# run only one of these at a time -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: true - -permissions: - pull-requests: read - contents: read - -jobs: - build_ts: - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest"] - version: [18, 20, 22, 24] - exclude: - # TODO: Test timeout jest work for some reason - - os: windows-latest - version: 22 - - runs-on: ${{ matrix.os }} - steps: - - if: runner.os == 'Linux' - run: | - sudo apt install libsecret-1-0 - - name: Setup Git LF - run: | - git config --global core.autocrlf false - - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v3 - id: filter - with: - filters: | - ts: - - "ts/**" - - ".github/workflows/build-ts.yml" - - uses: pnpm/action-setup@v4 - if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} - name: Install pnpm - with: - package_json_file: ts/package.json - - uses: actions/setup-node@v4 - if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} - with: - node-version: ${{ matrix.version }} - cache: "pnpm" - cache-dependency-path: ts/pnpm-lock.yaml - - name: Install dependencies - if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} - working-directory: ts - run: | - pnpm install --frozen-lockfile --strict-peer-dependencies - - name: Build - if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} - working-directory: ts - run: | - npm run build - # - name: Test - # if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} - # working-directory: ts - # run: | - # npm run test - - - name: Install Playwright Browsers - run: pnpm exec playwright install --with-deps - - name: Shell Tests - timeout-minutes: 180 - run: | - pnpm exec playwright test - working-directory: ts/packages/shell - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 - - - - name: Lint - if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} - working-directory: ts - run: | - npm run lint From 37450901ab39409822cc2dc6e792c9db753590cf Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 24 Jan 2025 23:56:37 -0800 Subject: [PATCH 023/138] more changes --- .github/workflows/playwright.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index eaf8b78fa..aa496a999 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -21,7 +21,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] - version: [18, 20, 22, 24] + version: [18, 20, 22] exclude: # TODO: Test timeout jest work for some reason - os: windows-latest @@ -72,6 +72,7 @@ jobs: - name: Install Playwright Browsers run: pnpm exec playwright install --with-deps + working-directory: ts/packages/shell - name: Shell Tests timeout-minutes: 180 run: | From ebb896f213ef7995d65d7d41dc9da9022d5f774e Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sat, 25 Jan 2025 00:18:02 -0800 Subject: [PATCH 024/138] trying npx --- .github/workflows/playwright.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index aa496a999..854e33320 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -75,9 +75,9 @@ jobs: working-directory: ts/packages/shell - name: Shell Tests timeout-minutes: 180 - run: | - pnpm exec playwright test - working-directory: ts/packages/shell + # run: | + # working-directory: ts/packages/shell + run: npx playwright test - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: From 1f1fd84ddb922448cdb513e30069cf39ffef50b7 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sat, 25 Jan 2025 00:23:56 -0800 Subject: [PATCH 025/138] fixed working directory for npx --- .github/workflows/playwright.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 854e33320..76739d325 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -78,6 +78,7 @@ jobs: # run: | # working-directory: ts/packages/shell run: npx playwright test + working-directory: ts/packages/shell - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: From b6623b14fd6742555bc176340a9253e36cc14501 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sat, 25 Jan 2025 10:51:40 -0800 Subject: [PATCH 026/138] Added new session test --- .../shell/test/sessionCommands.spec.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/ts/packages/shell/test/sessionCommands.spec.ts b/ts/packages/shell/test/sessionCommands.spec.ts index 05801d6b3..506697f3c 100644 --- a/ts/packages/shell/test/sessionCommands.spec.ts +++ b/ts/packages/shell/test/sessionCommands.spec.ts @@ -16,6 +16,7 @@ import { testSetup, waitForAgentMessage, } from "./testHelper"; +import { session } from "electron"; // Annotate entire file as serial. test.describe.configure({ mode: 'serial' }); @@ -46,6 +47,54 @@ test.describe("@session Commands", () => { // close the application await exitApplication(mainWindow); }); + + test("@session new/delete/list/info", async () => { + + // launch the app + const mainWindow: Page = await testSetup(); + + // create a new session so we have at least two + let msg = await sendUserRequestAndWaitForResponse(`@session new`, mainWindow); + expect(msg.toLowerCase()).toContain("new session created: "); + + // get the session count + msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); + let sessions = msg.split("\n"); + const originalSessionCount: number = sessions.length; + const sessionName: string = sessions[sessions.length - 1]; + + // issue delete session command + msg = await sendUserRequestAndWaitForResponse(`@session delete ${sessions[0]}`, mainWindow); + expect(msg.toLowerCase()).toContain("are you sure"); + + // click on cancel button + await mainWindow.locator(".choice-button", { hasText: "No" }).click(); + + // verify session not deleted + msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); + sessions = msg.split("\n"); + expect(sessions.length, "Session accidentally deleted.").toBe(originalSessionCount); + + // reissue delete session command + msg = await sendUserRequestAndWaitForResponse(`@session delete ${sessions[0]}`, mainWindow); + expect(msg.toLowerCase()).toContain("are you sure"); + + // click on delete button + await mainWindow.locator(".choice-button", { hasText: "Yes" }).click(); + + // get new session count + msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); + sessions = msg.split("\n"); + expect(sessions.length, "Session accidentally deleted.").toBe(originalSessionCount - 1); + + // get session info + msg = await sendUserRequestAndWaitForResponse(`@session info`, mainWindow); + sessions = msg.split("\n"); + expect(sessions[1], "Wrong session selected.").toContain(sessionName); + + // close the application + await exitApplication(mainWindow); + }); }); // TODO: Test action correction \ No newline at end of file From b600b48c8578c7d76837c3e22e37e188e413479f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sat, 25 Jan 2025 10:52:08 -0800 Subject: [PATCH 027/138] starting to work on alternate to #phraseDiv --- ts/packages/shell/test/testHelper.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 649c9fc41..155af1898 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -95,11 +95,21 @@ export function getAppPath(): string { * @param page The maing page from the electron host application. */ export async function sendUserRequest(prompt: string, page: Page) { + try { const locator: Locator = await page.locator("#phraseDiv"); - await locator.waitFor({ timeout: 120000, state: "visible" }); + await locator.waitFor({ timeout: 30000, state: "visible" }); await locator.focus(); await locator.fill(prompt); await locator.press("Enter"); + } catch (e) { + // TODO: find alternate method when the above fails. + console.log(e); + const l3 = await page.locator(".chat-input"); + + const l2 = await page.locator(".user-textarea"); + + const element = await page.waitForSelector("#phraseDiv"); + } } /** From 66cd16c3a3d72532b4666ca8034f1540d38a8b37 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 09:37:36 -0800 Subject: [PATCH 028/138] just running simple test in CI to debug test run failure --- .github/workflows/playwright.yml | 2 +- ts/packages/shell/playwright.config.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 76739d325..f32ae63b5 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -51,7 +51,7 @@ jobs: - uses: actions/setup-node@v4 if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} with: - node-version: ${{ matrix.version }} + node-version: 22 cache: "pnpm" cache-dependency-path: ts/pnpm-lock.yaml - name: Install dependencies diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts index e85c893ca..4d20f1f5d 100644 --- a/ts/packages/shell/playwright.config.ts +++ b/ts/packages/shell/playwright.config.ts @@ -54,6 +54,7 @@ export default defineConfig({ use: { ...devices["Desktop Chrome"] }, fullyParallel: false, dependencies: [ "global setup"], + testMatch: /simple\.spec\.ts/, }, ], From 6d641670615f6d8725c34c7c0e5379eadbcc1846 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 09:48:13 -0800 Subject: [PATCH 029/138] slight action tweak --- .github/workflows/playwright.yml | 19 ++++++------------- ts/packages/shell/test/simple.spec.ts | 12 ++++++++---- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index f32ae63b5..05b2eab43 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -44,35 +44,29 @@ jobs: - "ts/**" - ".github/workflows/build-ts.yml" - uses: pnpm/action-setup@v4 - if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} name: Install pnpm with: package_json_file: ts/package.json - uses: actions/setup-node@v4 - if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} with: - node-version: 22 + node-version: lts/* cache: "pnpm" - cache-dependency-path: ts/pnpm-lock.yaml - - name: Install dependencies - if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + cache-dependency-path: ts/pnpm-lock.yaml + - name: Install dependencies (pnpm) working-directory: ts run: | pnpm install --frozen-lockfile --strict-peer-dependencies + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + working-directory: ts/packages/shell - name: Build - if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} working-directory: ts run: | npm run build # - name: Test - # if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} # working-directory: ts # run: | # npm run test - - - name: Install Playwright Browsers - run: pnpm exec playwright install --with-deps - working-directory: ts/packages/shell - name: Shell Tests timeout-minutes: 180 # run: | @@ -88,7 +82,6 @@ jobs: - name: Lint - if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} working-directory: ts run: | npm run lint diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts index 483936cf3..31e4e5db2 100644 --- a/ts/packages/shell/test/simple.spec.ts +++ b/ts/packages/shell/test/simple.spec.ts @@ -1,4 +1,4 @@ -import test, { ElectronApplication,_electron, +import test, { ElectronApplication,Page,_electron, _electron as electron, } from "@playwright/test"; import { getAppPath, testSetup } from "./testHelper"; @@ -7,7 +7,11 @@ test("dummy", async () => { }); test("simple", async () => { - const window = await testSetup(); - await window.waitForTimeout(3000); - await window.close(); + const app: ElectronApplication = await electron.launch({ args: [getAppPath()] }); + const mainWindow: Page = await app.firstWindow(); + await mainWindow.bringToFront(); + await app.close(); + // const window = await testSetup(); + // await window.waitForTimeout(3000); + // await window.close(); }); \ No newline at end of file From c73ce00b1b0bc7a0961ad96a7f926a958d589f05 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 09:53:56 -0800 Subject: [PATCH 030/138] added test-results as artifacts --- .github/workflows/playwright.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 05b2eab43..56d879341 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -73,6 +73,12 @@ jobs: # working-directory: ts/packages/shell run: npx playwright test working-directory: ts/packages/shell + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: test-results + path: ts/packages/shell/test-results/ + retention-days: 30 - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: From d47be5e544240b72e00585f61d20924cbe460b51 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 09:54:31 -0800 Subject: [PATCH 031/138] corrected playwright report path --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 56d879341..ef97472d3 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -83,7 +83,7 @@ jobs: if: ${{ !cancelled() }} with: name: playwright-report - path: playwright-report/ + path: ts/packages/shell/playwright-report/ retention-days: 30 From 443e012381ed0312a3cda8b07e742781262e8fd7 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 10:44:08 -0800 Subject: [PATCH 032/138] debug cli message --- .github/workflows/playwright.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index ef97472d3..5e3b0e2d7 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -67,6 +67,10 @@ jobs: # working-directory: ts # run: | # npm run test + - name: Test CLI + run: | + npm run start:dev interactive + working-directory: ts/packages/cli - name: Shell Tests timeout-minutes: 180 # run: | @@ -78,12 +82,14 @@ jobs: with: name: test-results path: ts/packages/shell/test-results/ + overwrite: true retention-days: 30 - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: playwright-report path: ts/packages/shell/playwright-report/ + overwrite: true retention-days: 30 From 09c6084411c36d25ad39dd1c41154e43e0eaf2c3 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 10:50:12 -0800 Subject: [PATCH 033/138] another cli test --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 5e3b0e2d7..80aa748bb 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -69,7 +69,7 @@ jobs: # npm run test - name: Test CLI run: | - npm run start:dev interactive + npm run start:dev 'prompt' 'why is the sky blue' working-directory: ts/packages/cli - name: Shell Tests timeout-minutes: 180 From c96b4ec3fc90e25b385604fdd2ecc6dc94c62500 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 10:56:03 -0800 Subject: [PATCH 034/138] added getkeys so we can run shell tests --- .github/workflows/playwright.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 80aa748bb..c326e2525 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -67,10 +67,20 @@ jobs: # working-directory: ts # run: | # npm run test - - name: Test CLI + # - name: Test CLI + # run: | + # npm run start:dev 'prompt' 'why is the sky blue' + # working-directory: ts/packages/cli + - name: Login to Azure + uses: azure/login@v2.2.0 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} + + - name: Get Keys run: | - npm run start:dev 'prompt' 'why is the sky blue' - working-directory: ts/packages/cli + node ../tools/scripts/getKeys.mjs - name: Shell Tests timeout-minutes: 180 # run: | From b2f0f61c6f42dd5a764ab379232c88f0b1547729 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 10:58:58 -0800 Subject: [PATCH 035/138] added necessary perms to write id token --- .github/workflows/playwright.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index c326e2525..d4f8d2507 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -6,6 +6,10 @@ on: push: branches: [ dev/robgruen/electron_tests ] +permissions: + id-token: write + contents: read + # run only one of these at a time concurrency: group: ${{ github.workflow }} From e3b3c481702b2b000e1ce1259c0e1d918c4942e6 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 11:00:36 -0800 Subject: [PATCH 036/138] removed duplicate --- .github/workflows/playwright.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index d4f8d2507..e9fc5dd04 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -15,10 +15,6 @@ concurrency: group: ${{ github.workflow }} cancel-in-progress: true -permissions: - pull-requests: read - contents: read - jobs: build_ts: strategy: From d33ca65533ab3cc8c125d00f4210f954a02c6c66 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 11:12:04 -0800 Subject: [PATCH 037/138] fixed getkeys path --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index e9fc5dd04..a4278988a 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -80,7 +80,7 @@ jobs: - name: Get Keys run: | - node ../tools/scripts/getKeys.mjs + node ts/tools/scripts/getKeys.mjs - name: Shell Tests timeout-minutes: 180 # run: | From 7f410ffdb52b6ae09f5f44bddcea4f4008b4ced6 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 11:18:06 -0800 Subject: [PATCH 038/138] set auth-type for azure login --- .github/workflows/playwright.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index a4278988a..55e9ea45a 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -77,6 +77,7 @@ jobs: client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} + auth-type: IDENTITY - name: Get Keys run: | From c03394684679a26263d189d2f64af3921b242985 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 11:32:24 -0800 Subject: [PATCH 039/138] removing identity based auth and updating getKeys to try to get keys without elevation. --- .github/workflows/playwright.yml | 2 +- ts/tools/scripts/getKeys.mjs | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 55e9ea45a..b08f0c0f6 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -77,7 +77,7 @@ jobs: client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} - auth-type: IDENTITY + #auth-type: IDENTITY - name: Get Keys run: | diff --git a/ts/tools/scripts/getKeys.mjs b/ts/tools/scripts/getKeys.mjs index 2e66e57cc..7351f3e66 100644 --- a/ts/tools/scripts/getKeys.mjs +++ b/ts/tools/scripts/getKeys.mjs @@ -31,16 +31,23 @@ async function getSecretListWithElevation(keyVaultClient, vaultName) { throw e; } - console.warn(chalk.yellowBright("Elevating to get secrets...")); - const pimClient = await getPIMClient(); - await pimClient.elevate({ - requestType: "SelfActivate", - roleName: "Key Vault Administrator", - expirationType: "AfterDuration", - expirationDuration: "PT5M", // activate for 5 minutes - }); - // Wait for the role to be activated - console.warn(chalk.yellowBright("Waiting 5 seconds...")); + try { + console.warn(chalk.yellowBright("Elevating to get secrets...")); + const pimClient = await getPIMClient(); + await pimClient.elevate({ + requestType: "SelfActivate", + roleName: "Key Vault Administrator", + expirationType: "AfterDuration", + expirationDuration: "PT5M", // activate for 5 minutes + }); + + // Wait for the role to be activated + console.log(chalk.green("Elevation successful.")); + console.warn(chalk.yellowBright("Waiting 5 seconds...")); + } catch (e) { + console.warn(chalk.yellow("Elevation failed...attempting to get secrets without elevation.")); + } + await new Promise((res) => setTimeout(res, 5000)); return await keyVaultClient.getSecrets(vaultName); } From f0460df48cd7f4cbd93070e79d8a15c7dea3c1a0 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 11:38:08 -0800 Subject: [PATCH 040/138] contue on failure to elevate --- ts/tools/scripts/getKeys.mjs | 3 ++- ts/tools/scripts/lib/pimClient.mjs | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ts/tools/scripts/getKeys.mjs b/ts/tools/scripts/getKeys.mjs index 7351f3e66..b5be86e2e 100644 --- a/ts/tools/scripts/getKeys.mjs +++ b/ts/tools/scripts/getKeys.mjs @@ -39,6 +39,7 @@ async function getSecretListWithElevation(keyVaultClient, vaultName) { roleName: "Key Vault Administrator", expirationType: "AfterDuration", expirationDuration: "PT5M", // activate for 5 minutes + continueOnFailure: true }); // Wait for the role to be activated @@ -47,7 +48,7 @@ async function getSecretListWithElevation(keyVaultClient, vaultName) { } catch (e) { console.warn(chalk.yellow("Elevation failed...attempting to get secrets without elevation.")); } - + await new Promise((res) => setTimeout(res, 5000)); return await keyVaultClient.getSecrets(vaultName); } diff --git a/ts/tools/scripts/lib/pimClient.mjs b/ts/tools/scripts/lib/pimClient.mjs index aa3310a84..aac50b561 100644 --- a/ts/tools/scripts/lib/pimClient.mjs +++ b/ts/tools/scripts/lib/pimClient.mjs @@ -44,7 +44,7 @@ class AzPIMClient { console.log("Looking up role information..."); const roleAssignmentScheduleRequestName = randomUUID(); const parameters = { - principalId: await this.getPrincipalId(), + principalId: await this.getPrincipalId(options?.continueOnFailure), requestType: options.requestType, roleDefinitionId: await this.getRoleDefinitionId( client, @@ -146,7 +146,7 @@ class AzPIMClient { } } - async getPrincipalId() { + async getPrincipalId(continueOnFailure = false) { try { const accountDetails = JSON.parse( await execAsync("az ad signed-in-user show"), @@ -155,7 +155,10 @@ class AzPIMClient { return accountDetails.id; } catch (e) { console.log(e); - fatal(12, "Unable to get principal id of the current user."); + + if (!continueOnFailure) { + fatal(12, "Unable to get principal id of the current user."); + } } } } From fc9cba08252cbbc910ceacd48ae37e6dfa395fe1 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 11:43:24 -0800 Subject: [PATCH 041/138] Added missing throw --- ts/tools/scripts/lib/pimClient.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ts/tools/scripts/lib/pimClient.mjs b/ts/tools/scripts/lib/pimClient.mjs index aac50b561..322312dfd 100644 --- a/ts/tools/scripts/lib/pimClient.mjs +++ b/ts/tools/scripts/lib/pimClient.mjs @@ -158,6 +158,8 @@ class AzPIMClient { if (!continueOnFailure) { fatal(12, "Unable to get principal id of the current user."); + } else { + throw "Unable to get principal id of the current user."; } } } From 2cd1afc9a036af167abce7c9b7ecff36f64bdfca Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 11:52:06 -0800 Subject: [PATCH 042/138] minor refactor --- ts/tools/scripts/getKeys.mjs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ts/tools/scripts/getKeys.mjs b/ts/tools/scripts/getKeys.mjs index b5be86e2e..00f979608 100644 --- a/ts/tools/scripts/getKeys.mjs +++ b/ts/tools/scripts/getKeys.mjs @@ -45,11 +45,10 @@ async function getSecretListWithElevation(keyVaultClient, vaultName) { // Wait for the role to be activated console.log(chalk.green("Elevation successful.")); console.warn(chalk.yellowBright("Waiting 5 seconds...")); + await new Promise((res) => setTimeout(res, 5000)); } catch (e) { console.warn(chalk.yellow("Elevation failed...attempting to get secrets without elevation.")); - } - - await new Promise((res) => setTimeout(res, 5000)); + } return await keyVaultClient.getSecrets(vaultName); } } From d195af847fc94f61661c392a515f0699f35ee324 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 11:56:40 -0800 Subject: [PATCH 043/138] added cli debugging step back in --- .github/workflows/playwright.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index b08f0c0f6..b6282e325 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -67,21 +67,19 @@ jobs: # working-directory: ts # run: | # npm run test - # - name: Test CLI - # run: | - # npm run start:dev 'prompt' 'why is the sky blue' - # working-directory: ts/packages/cli - name: Login to Azure uses: azure/login@v2.2.0 with: client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} - #auth-type: IDENTITY - - name: Get Keys run: | node ts/tools/scripts/getKeys.mjs + - name: Test CLI + run: | + npm run start:dev 'prompt' 'why is the sky blue' + working-directory: ts/packages/cli - name: Shell Tests timeout-minutes: 180 # run: | From f21d1dbbe9610b4b382b5713316215c5212f335e Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 11:58:51 -0800 Subject: [PATCH 044/138] updated working directory for .env file --- .github/workflows/playwright.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index b6282e325..7acdba01d 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -75,7 +75,8 @@ jobs: subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} - name: Get Keys run: | - node ts/tools/scripts/getKeys.mjs + node tools/scripts/getKeys.mjs + working-directory: ts - name: Test CLI run: | npm run start:dev 'prompt' 'why is the sky blue' From 0576d89f81d5ef8926cbb318173db5eeda7a9e64 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 12:07:39 -0800 Subject: [PATCH 045/138] updated test to just run on windows for now --- .github/workflows/playwright.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 7acdba01d..51c389a0f 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -20,7 +20,8 @@ jobs: strategy: fail-fast: false matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest"] + # os: ["ubuntu-latest", "windows-latest", "macos-latest"] + os: ["windows-latest"] version: [18, 20, 22] exclude: # TODO: Test timeout jest work for some reason @@ -77,7 +78,7 @@ jobs: run: | node tools/scripts/getKeys.mjs working-directory: ts - - name: Test CLI + - name: Test CLI - verify .env & endpoint connectivity run: | npm run start:dev 'prompt' 'why is the sky blue' working-directory: ts/packages/cli @@ -103,7 +104,7 @@ jobs: retention-days: 30 - - name: Lint - working-directory: ts - run: | - npm run lint + # - name: Lint + # working-directory: ts + # run: | + # npm run lint From 9a204ea5c92f76b30918810381c9ac05245166c4 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 12:07:55 -0800 Subject: [PATCH 046/138] enabled all tests --- ts/packages/shell/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts index 4d20f1f5d..4486e24b8 100644 --- a/ts/packages/shell/playwright.config.ts +++ b/ts/packages/shell/playwright.config.ts @@ -54,7 +54,7 @@ export default defineConfig({ use: { ...devices["Desktop Chrome"] }, fullyParallel: false, dependencies: [ "global setup"], - testMatch: /simple\.spec\.ts/, + //testMatch: /simple\.spec\.ts/, }, ], From b3adc3f20ea86a7844e60231c11d09a89e6b56d2 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 12:30:56 -0800 Subject: [PATCH 047/138] added cron placeholder --- .github/workflows/playwright.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 51c389a0f..285f9fa61 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -5,6 +5,8 @@ name: Shell Tests on: push: branches: [ dev/robgruen/electron_tests ] + # schedule: + # - cron: "0 0 * * *" permissions: id-token: write @@ -50,7 +52,7 @@ jobs: package_json_file: ts/package.json - uses: actions/setup-node@v4 with: - node-version: lts/* + node-version: ${{ matrix.version }} cache: "pnpm" cache-dependency-path: ts/pnpm-lock.yaml - name: Install dependencies (pnpm) From f9bafc20d796a88cf7510d0a14c2f6588dd4afe0 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 12:34:57 -0800 Subject: [PATCH 048/138] renamed method --- ts/packages/shell/test/configCommands.spec.ts | 6 +++--- ts/packages/shell/test/hostWindow.spec.ts | 10 +++++----- ts/packages/shell/test/sessionCommands.spec.ts | 9 +++++---- ts/packages/shell/test/simple.spec.ts | 5 +---- ts/packages/shell/test/testHelper.ts | 6 ++++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ts/packages/shell/test/configCommands.spec.ts b/ts/packages/shell/test/configCommands.spec.ts index 44311b3ec..ac5c4f260 100644 --- a/ts/packages/shell/test/configCommands.spec.ts +++ b/ts/packages/shell/test/configCommands.spec.ts @@ -13,7 +13,7 @@ import { getLastAgentMessage, sendUserRequest, sendUserRequestAndWaitForResponse, - testSetup, + startShell, waitForAgentMessage, } from "./testHelper"; @@ -25,7 +25,7 @@ test.describe("@config Commands", () => { test("@config dev", async () => { // launch the app - const mainWindow: Page = await testSetup(); + const mainWindow: Page = await startShell(); let msg = await sendUserRequestAndWaitForResponse(`@config dev`, mainWindow); @@ -46,7 +46,7 @@ test.describe("@config Commands", () => { test("@config schema", async () => { // launch the app - const mainWindow: Page = await testSetup(); + const mainWindow: Page = await startShell(); let msg = await sendUserRequestAndWaitForResponse(`@config schema oracle`, mainWindow); diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index bdf7bad3e..80138fd4f 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -14,7 +14,7 @@ import { sendUserRequest, sendUserRequestAndWaitForResponse, sendUserRequestFast, - testSetup, + startShell, waitForAgentMessage, } from "./testHelper"; import { exit } from "process"; @@ -30,7 +30,7 @@ test.describe("Shell interface tests", () => { test("remember window position", async () => { let agentMessageCount = 0; - const firstWindow = await testSetup(); + const firstWindow = await startShell(); // verify shell title const title = await firstWindow.title(); @@ -54,7 +54,7 @@ test.describe("Shell interface tests", () => { await exitApplication(firstWindow); // restart the app - const newWindow: Page = await testSetup(); + const newWindow: Page = await startShell(); // get window size/position const msg = await sendUserRequestAndWaitForResponse(`@shell show raw`, newWindow); @@ -80,7 +80,7 @@ test.describe("Shell interface tests", () => { */ test("zoom level", async () => { // start the app - const mainWindow = await testSetup(); + const mainWindow = await startShell(); // test 80% zoom await testZoomLevel(0.8, mainWindow); @@ -116,7 +116,7 @@ test.describe("Shell interface tests", () => { let agentMessageCount = 0; // start the app - const mainWindow = await testSetup(); + const mainWindow = await startShell(); // make sure send button is disabled const sendButton = await mainWindow.locator("#sendbutton"); diff --git a/ts/packages/shell/test/sessionCommands.spec.ts b/ts/packages/shell/test/sessionCommands.spec.ts index 506697f3c..9fd3c1ed4 100644 --- a/ts/packages/shell/test/sessionCommands.spec.ts +++ b/ts/packages/shell/test/sessionCommands.spec.ts @@ -13,7 +13,7 @@ import { getLastAgentMessage, sendUserRequest, sendUserRequestAndWaitForResponse, - testSetup, + startShell, waitForAgentMessage, } from "./testHelper"; import { session } from "electron"; @@ -23,10 +23,11 @@ test.describe.configure({ mode: 'serial' }); test.describe("@session Commands", () => { - test("@session new/list", async () => { + test("@session new/list", async ({}, testInfo) => { + console.log(`Starting test '${testInfo.title}'`); // launch the app - const mainWindow: Page = await testSetup(); + const mainWindow: Page = await startShell(); // get the session count let msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); @@ -51,7 +52,7 @@ test.describe("@session Commands", () => { test("@session new/delete/list/info", async () => { // launch the app - const mainWindow: Page = await testSetup(); + const mainWindow: Page = await startShell(); // create a new session so we have at least two let msg = await sendUserRequestAndWaitForResponse(`@session new`, mainWindow); diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts index 31e4e5db2..50526f8e1 100644 --- a/ts/packages/shell/test/simple.spec.ts +++ b/ts/packages/shell/test/simple.spec.ts @@ -1,6 +1,6 @@ import test, { ElectronApplication,Page,_electron, _electron as electron, } from "@playwright/test"; -import { getAppPath, testSetup } from "./testHelper"; +import { getAppPath, startShell } from "./testHelper"; test("dummy", async () => { // do nothing @@ -11,7 +11,4 @@ test("simple", async () => { const mainWindow: Page = await app.firstWindow(); await mainWindow.bringToFront(); await app.close(); - // const window = await testSetup(); - // await window.waitForTimeout(3000); - // await window.close(); }); \ No newline at end of file diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 155af1898..5c44a6be1 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -17,8 +17,7 @@ const runningApplications: Map = new Map { - +export async function startShell(): Promise { // this is needed to isolate these tests session from other concurrently running tests process.env["INSTANCE_NAME"] = `test_${process.env["TEST_WORKER_INDEX"]}_${process.env["TEST_PARALLEL_INDEX"]}`; @@ -105,10 +104,13 @@ export async function sendUserRequest(prompt: string, page: Page) { // TODO: find alternate method when the above fails. console.log(e); const l3 = await page.locator(".chat-input"); + console.log(`Found ${l3}`); const l2 = await page.locator(".user-textarea"); + console.log(`Found ${l2}`); const element = await page.waitForSelector("#phraseDiv"); + console.log(`Found ${element}`); } } From 6728930f577da6bf4c973aae9bd5cad8cdf0a2c9 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 13:03:51 -0800 Subject: [PATCH 049/138] Added console logging. --- ts/packages/shell/test/configCommands.spec.ts | 6 ++++-- ts/packages/shell/test/hostWindow.spec.ts | 12 +++++++++--- ts/packages/shell/test/sessionCommands.spec.ts | 5 +++-- ts/packages/shell/test/simple.spec.ts | 4 +++- ts/packages/shell/test/testHelper.ts | 5 +++-- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/ts/packages/shell/test/configCommands.spec.ts b/ts/packages/shell/test/configCommands.spec.ts index ac5c4f260..4c67d8e24 100644 --- a/ts/packages/shell/test/configCommands.spec.ts +++ b/ts/packages/shell/test/configCommands.spec.ts @@ -22,7 +22,8 @@ test.describe.configure({ mode: 'serial' }); test.describe("@config Commands", () => { - test("@config dev", async () => { + test("@config dev", async ({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); // launch the app const mainWindow: Page = await startShell(); @@ -43,7 +44,8 @@ test.describe("@config Commands", () => { await exitApplication(mainWindow); }); - test("@config schema", async () => { + test("@config schema", async ({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); // launch the app const mainWindow: Page = await startShell(); diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index 80138fd4f..ced811e29 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -27,7 +27,9 @@ test.describe("Shell interface tests", () => { /** * Test to ensure that the shell recall startup layout (position, size) */ - test("remember window position", async () => { + test("remember window position", async ({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); + let agentMessageCount = 0; const firstWindow = await startShell(); @@ -78,7 +80,9 @@ test.describe("Shell interface tests", () => { /** * Ensures zoom level is working */ - test("zoom level", async () => { + test("zoom level", async ({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); + // start the app const mainWindow = await startShell(); @@ -112,7 +116,9 @@ test.describe("Shell interface tests", () => { /** * Ensure send button is behaving */ - test("send button state", async () => { + test("send button state", async ({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); + let agentMessageCount = 0; // start the app diff --git a/ts/packages/shell/test/sessionCommands.spec.ts b/ts/packages/shell/test/sessionCommands.spec.ts index 9fd3c1ed4..9f55d0fd3 100644 --- a/ts/packages/shell/test/sessionCommands.spec.ts +++ b/ts/packages/shell/test/sessionCommands.spec.ts @@ -24,7 +24,7 @@ test.describe.configure({ mode: 'serial' }); test.describe("@session Commands", () => { test("@session new/list", async ({}, testInfo) => { - console.log(`Starting test '${testInfo.title}'`); + console.log(`Running test '${testInfo.title}`); // launch the app const mainWindow: Page = await startShell(); @@ -49,7 +49,8 @@ test.describe("@session Commands", () => { await exitApplication(mainWindow); }); - test("@session new/delete/list/info", async () => { + test("@session new/delete/list/info", async ({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); // launch the app const mainWindow: Page = await startShell(); diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts index 50526f8e1..beafa690a 100644 --- a/ts/packages/shell/test/simple.spec.ts +++ b/ts/packages/shell/test/simple.spec.ts @@ -6,7 +6,9 @@ test("dummy", async () => { // do nothing }); -test("simple", async () => { +test("simple", async ({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); + const app: ElectronApplication = await electron.launch({ args: [getAppPath()] }); const mainWindow: Page = await app.firstWindow(); await mainWindow.bringToFront(); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 5c44a6be1..691735810 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -6,7 +6,8 @@ import { _electron as electron, ElectronApplication, Locator, - Page } from "@playwright/test"; + Page, + TestDetails} from "@playwright/test"; import { profile } from "node:console"; import fs from "node:fs"; import path from "node:path"; @@ -18,6 +19,7 @@ const runningApplications: Map = new Map { + // this is needed to isolate these tests session from other concurrently running tests process.env["INSTANCE_NAME"] = `test_${process.env["TEST_WORKER_INDEX"]}_${process.env["TEST_PARALLEL_INDEX"]}`; @@ -35,7 +37,6 @@ export async function startShell(): Promise { throw new Error("Application instance already running. Did you shutdown cleanly?"); } - // start the app console.log(`Starting ${process.env["INSTANCE_NAME"]}`); const app: ElectronApplication = await electron.launch({ args: [getAppPath()] }); runningApplications.set(process.env["INSTANCE_NAME"]!, app); From a2844d623e4176bbe348fa594e3b3a5f3909b18f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 15:13:08 -0800 Subject: [PATCH 050/138] alternate selector for #phraseDiv --- ts/packages/shell/test/testHelper.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 691735810..d260ad747 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -96,11 +96,12 @@ export function getAppPath(): string { */ export async function sendUserRequest(prompt: string, page: Page) { try { - const locator: Locator = await page.locator("#phraseDiv"); - await locator.waitFor({ timeout: 30000, state: "visible" }); - await locator.focus(); - await locator.fill(prompt); - await locator.press("Enter"); + //const locator: Locator = await page.locator("#phraseDiv"); + const locator: Locator = await page.locator(".chat-input"); + await locator.waitFor({ timeout: 30000, state: "visible" }); + await locator.focus(); + await locator.fill(prompt); + await locator.press("Enter"); } catch (e) { // TODO: find alternate method when the above fails. console.log(e); @@ -121,7 +122,7 @@ export async function sendUserRequest(prompt: string, page: Page) { * @param page The maing page from the electron host application. */ export async function sendUserRequestFast(prompt: string, page: Page) { - const locator: Locator = await page.locator("#phraseDiv"); + const locator: Locator = await page.locator(".chat-input"); await locator.waitFor({ timeout: 120000, state: "visible" }); await locator.fill(prompt); page.keyboard.down("Enter"); From 3039a575d1dfab3054f232b0373fa41bc68fa451 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 15:25:20 -0800 Subject: [PATCH 051/138] tried another selector --- ts/packages/shell/test/testHelper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index d260ad747..facb4cb3c 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -97,7 +97,7 @@ export function getAppPath(): string { export async function sendUserRequest(prompt: string, page: Page) { try { //const locator: Locator = await page.locator("#phraseDiv"); - const locator: Locator = await page.locator(".chat-input"); + const locator: Locator = await page.locator(".user-textarea"); await locator.waitFor({ timeout: 30000, state: "visible" }); await locator.focus(); await locator.fill(prompt); @@ -122,7 +122,7 @@ export async function sendUserRequest(prompt: string, page: Page) { * @param page The maing page from the electron host application. */ export async function sendUserRequestFast(prompt: string, page: Page) { - const locator: Locator = await page.locator(".chat-input"); + const locator: Locator = await page.locator(".user-textarea"); await locator.waitFor({ timeout: 120000, state: "visible" }); await locator.fill(prompt); page.keyboard.down("Enter"); From bc0b33b86649496e8af63c1f62b9c40c0945b5e5 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 15:45:29 -0800 Subject: [PATCH 052/138] used testid attribute to find elements --- ts/packages/shell/src/renderer/src/chatInput.ts | 1 + ts/packages/shell/test/testHelper.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ts/packages/shell/src/renderer/src/chatInput.ts b/ts/packages/shell/src/renderer/src/chatInput.ts index 98b5266bf..1bf928434 100644 --- a/ts/packages/shell/src/renderer/src/chatInput.ts +++ b/ts/packages/shell/src/renderer/src/chatInput.ts @@ -37,6 +37,7 @@ export class ExpandableTextarea { this.textEntry.contentEditable = "true"; this.textEntry.role = "textbox"; this.textEntry.id = id; + this.textEntry.setAttribute("data-testid", id); this.textEntry.addEventListener("keydown", (event) => { if (this.entryHandlers.onKeydown !== undefined) { if (!this.entryHandlers.onKeydown(this, event)) { diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index facb4cb3c..4be0fb2fd 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -95,9 +95,18 @@ export function getAppPath(): string { * @param page The maing page from the electron host application. */ export async function sendUserRequest(prompt: string, page: Page) { + + // let locator: Locator | undefined = undefined; + + // // try to get the locator in two different ways and repeat that twice before failing + // try { + + // } + + try { - //const locator: Locator = await page.locator("#phraseDiv"); - const locator: Locator = await page.locator(".user-textarea"); + const locator: Locator = await page.getByTestId("phraseDiv"); + //const locator: Locator = await page.locator(".user-textarea"); await locator.waitFor({ timeout: 30000, state: "visible" }); await locator.focus(); await locator.fill(prompt); From 92323a0abb83c69efbcf47f9bab6cd2320740c4b Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 15:54:43 -0800 Subject: [PATCH 053/138] increased timeouts --- ts/packages/shell/test/testHelper.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 4be0fb2fd..5e99d394b 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -107,10 +107,10 @@ export async function sendUserRequest(prompt: string, page: Page) { try { const locator: Locator = await page.getByTestId("phraseDiv"); //const locator: Locator = await page.locator(".user-textarea"); - await locator.waitFor({ timeout: 30000, state: "visible" }); - await locator.focus(); - await locator.fill(prompt); - await locator.press("Enter"); + await locator.waitFor({ timeout: 60000, state: "visible" }); + await locator.focus({ timeout: 60000 }); + await locator.fill(prompt, { timeout: 60000 }); + await locator.press("Enter", { timeout: 60000 }); } catch (e) { // TODO: find alternate method when the above fails. console.log(e); @@ -133,7 +133,7 @@ export async function sendUserRequest(prompt: string, page: Page) { export async function sendUserRequestFast(prompt: string, page: Page) { const locator: Locator = await page.locator(".user-textarea"); await locator.waitFor({ timeout: 120000, state: "visible" }); - await locator.fill(prompt); + await locator.fill(prompt, { timeout: 60000 }); page.keyboard.down("Enter"); } From 7edaa32cc9a5d30a6c2efbe964c94539b0e16d65 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 15:58:31 -0800 Subject: [PATCH 054/138] removed testid locator...not much better. --- ts/packages/shell/src/renderer/src/chatInput.ts | 1 - ts/packages/shell/test/testHelper.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ts/packages/shell/src/renderer/src/chatInput.ts b/ts/packages/shell/src/renderer/src/chatInput.ts index 1bf928434..98b5266bf 100644 --- a/ts/packages/shell/src/renderer/src/chatInput.ts +++ b/ts/packages/shell/src/renderer/src/chatInput.ts @@ -37,7 +37,6 @@ export class ExpandableTextarea { this.textEntry.contentEditable = "true"; this.textEntry.role = "textbox"; this.textEntry.id = id; - this.textEntry.setAttribute("data-testid", id); this.textEntry.addEventListener("keydown", (event) => { if (this.entryHandlers.onKeydown !== undefined) { if (!this.entryHandlers.onKeydown(this, event)) { diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 5e99d394b..344c11e31 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -105,7 +105,7 @@ export async function sendUserRequest(prompt: string, page: Page) { try { - const locator: Locator = await page.getByTestId("phraseDiv"); + const locator: Locator = await page.locator("#phraseDiv"); //const locator: Locator = await page.locator(".user-textarea"); await locator.waitFor({ timeout: 60000, state: "visible" }); await locator.focus({ timeout: 60000 }); From a9c8efe1b3e9479fa33062ccb5d37c6401692714 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 16:03:55 -0800 Subject: [PATCH 055/138] reduced timeout --- ts/packages/shell/test/testHelper.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 344c11e31..067d57b0b 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -107,10 +107,10 @@ export async function sendUserRequest(prompt: string, page: Page) { try { const locator: Locator = await page.locator("#phraseDiv"); //const locator: Locator = await page.locator(".user-textarea"); - await locator.waitFor({ timeout: 60000, state: "visible" }); - await locator.focus({ timeout: 60000 }); - await locator.fill(prompt, { timeout: 60000 }); - await locator.press("Enter", { timeout: 60000 }); + await locator.waitFor({ timeout: 30000, state: "visible" }); + await locator.focus({ timeout: 30000 }); + await locator.fill(prompt, { timeout: 30000 }); + await locator.press("Enter", { timeout: 30000 }); } catch (e) { // TODO: find alternate method when the above fails. console.log(e); @@ -133,7 +133,7 @@ export async function sendUserRequest(prompt: string, page: Page) { export async function sendUserRequestFast(prompt: string, page: Page) { const locator: Locator = await page.locator(".user-textarea"); await locator.waitFor({ timeout: 120000, state: "visible" }); - await locator.fill(prompt, { timeout: 60000 }); + await locator.fill(prompt, { timeout: 30000 }); page.keyboard.down("Enter"); } From df0aad7aa93a3f3c3154e2d3d5c3a57341847f78 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 16:15:58 -0800 Subject: [PATCH 056/138] turned on playwright debug mode --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 285f9fa61..9c3679f41 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -88,7 +88,7 @@ jobs: timeout-minutes: 180 # run: | # working-directory: ts/packages/shell - run: npx playwright test + run: npx playwright test --debug working-directory: ts/packages/shell - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} From 1e8104728e1bd5a36878c07744819a89204411ac Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 18:17:25 -0800 Subject: [PATCH 057/138] More robust page finding logic. --- ts/packages/shell/playwright.config.ts | 3 +- ts/packages/shell/test/configCommands.spec.ts | 1 - .../shell/test/sessionCommands.spec.ts | 45 ++++++++++++- ts/packages/shell/test/testHelper.ts | 65 +++++++++++-------- 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts index 4486e24b8..e9117e250 100644 --- a/ts/packages/shell/playwright.config.ts +++ b/ts/packages/shell/playwright.config.ts @@ -35,7 +35,8 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", }, - + + maxFailures: 0, timeout: 300_000, // Set global timeout to 120 seconds /* Configure projects for major browsers */ diff --git a/ts/packages/shell/test/configCommands.spec.ts b/ts/packages/shell/test/configCommands.spec.ts index 4c67d8e24..e5b88a9a4 100644 --- a/ts/packages/shell/test/configCommands.spec.ts +++ b/ts/packages/shell/test/configCommands.spec.ts @@ -10,7 +10,6 @@ import test, { import { exitApplication, getAppPath, - getLastAgentMessage, sendUserRequest, sendUserRequestAndWaitForResponse, startShell, diff --git a/ts/packages/shell/test/sessionCommands.spec.ts b/ts/packages/shell/test/sessionCommands.spec.ts index 9f55d0fd3..0cd3a1a9b 100644 --- a/ts/packages/shell/test/sessionCommands.spec.ts +++ b/ts/packages/shell/test/sessionCommands.spec.ts @@ -81,7 +81,7 @@ test.describe("@session Commands", () => { msg = await sendUserRequestAndWaitForResponse(`@session delete ${sessions[0]}`, mainWindow); expect(msg.toLowerCase()).toContain("are you sure"); - // click on delete button + // click on Yes button await mainWindow.locator(".choice-button", { hasText: "Yes" }).click(); // get new session count @@ -97,6 +97,49 @@ test.describe("@session Commands", () => { // close the application await exitApplication(mainWindow); }); + + test("@session reset/clear", async ({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); + + // launch the app + const mainWindow: Page = await startShell(); + + // reset + let msg = await sendUserRequestAndWaitForResponse(`@session reset`, mainWindow); + expect(msg).toContain("Session settings revert to default."); + + // issue clear session command + msg = await sendUserRequestAndWaitForResponse(`@session clear`, mainWindow); + expect(msg.toLowerCase()).toContain("are you sure"); + + // click on Yes button + await mainWindow.locator(".choice-button", { hasText: "Yes" }).click(); + + // close the application + await exitApplication(mainWindow); + }); + + test("@session open", async({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); + + // launch the app + const mainWindow: Page = await startShell(); + + // create a new session + let msg = await sendUserRequestAndWaitForResponse(`@session new`, mainWindow); + expect(msg.toLowerCase()).toContain("new session created: "); + + // get the session list + msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); + const sessions: string[] = msg.split("\n"); + + // open the earlier session + msg = await sendUserRequestAndWaitForResponse(`@session open ${sessions[0]}`, mainWindow); + expect(msg, `Unexpected session openend!`).toBe(`Session opened: ${sessions[0]}`); + + // close the application + await exitApplication(mainWindow); + }); }); // TODO: Test action correction \ No newline at end of file diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 067d57b0b..8bbab2c33 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -37,16 +37,19 @@ export async function startShell(): Promise { throw new Error("Application instance already running. Did you shutdown cleanly?"); } - console.log(`Starting ${process.env["INSTANCE_NAME"]}`); + console.log(`Starting electron instance '${process.env["INSTANCE_NAME"]}'`); const app: ElectronApplication = await electron.launch({ args: [getAppPath()] }); runningApplications.set(process.env["INSTANCE_NAME"]!, app); - // get the main window - const mainWindow: Page = await app.firstWindow(); - await mainWindow.waitForLoadState("domcontentloaded"); + app.on('window', async (data) => { + console.log(`New Window created! ${await data.content()}`); + }); + + // get the main window + const mainWindow: Page = await getMainWindow(app); // wait for agent greeting - await waitForAgentMessage(mainWindow, 10000, 1); + await waitForAgentMessage(mainWindow, 30000, 1); return mainWindow; @@ -65,6 +68,24 @@ export async function startShell(): Promise { throw new Error(`Failed to start electrom app after ${maxRetries} attemps.`); } +async function getMainWindow(app: ElectronApplication): Promise { + const window: Page = await app.firstWindow(); + await window.waitForLoadState("domcontentloaded"); + + // is this the correct window? + if ((await window.title()).length > 0) { + return window; + } + + // if we change the # of windows beyond 2 we'll have to update this function to correctly disambiguate which window is the correct one + if (app.windows.length > 2) { + throw "Please update this logic to select the correct main window. (testHelper.ts->getMainWindow())"; + } + + // since there are only two windows we know that if the first one isn't the right one we can just return the second one + return app.windows[app.windows.length - 1]; +} + /** * Cleanly shuts down any running instance of the Shell * @param page The main window of the application @@ -96,32 +117,24 @@ export function getAppPath(): string { */ export async function sendUserRequest(prompt: string, page: Page) { - // let locator: Locator | undefined = undefined; - - // // try to get the locator in two different ways and repeat that twice before failing - // try { - - // } - - try { - const locator: Locator = await page.locator("#phraseDiv"); + const locator: Locator = page.locator("#phraseDiv"); //const locator: Locator = await page.locator(".user-textarea"); await locator.waitFor({ timeout: 30000, state: "visible" }); await locator.focus({ timeout: 30000 }); await locator.fill(prompt, { timeout: 30000 }); await locator.press("Enter", { timeout: 30000 }); + + return; } catch (e) { - // TODO: find alternate method when the above fails. - console.log(e); - const l3 = await page.locator(".chat-input"); - console.log(`Found ${l3}`); - - const l2 = await page.locator(".user-textarea"); - console.log(`Found ${l2}`); - - const element = await page.waitForSelector("#phraseDiv"); - console.log(`Found ${element}`); + // // TODO: find alternate method when the above fails. + // console.log(e); + + // let title = await page.title(); + // console.log(title); + + // const c = await page.content(); + // console.log(c); } } @@ -131,7 +144,7 @@ export async function sendUserRequest(prompt: string, page: Page) { * @param page The maing page from the electron host application. */ export async function sendUserRequestFast(prompt: string, page: Page) { - const locator: Locator = await page.locator(".user-textarea"); + const locator: Locator = page.locator("#phraseDiv"); await locator.waitFor({ timeout: 120000, state: "visible" }); await locator.fill(prompt, { timeout: 30000 }); page.keyboard.down("Enter"); @@ -149,7 +162,7 @@ export async function sendUserRequestAndWaitForResponse(prompt: string, page: Pa await sendUserRequest(prompt, page); // wait for agent response - await waitForAgentMessage(page, 10000, locators.length + 1); + await waitForAgentMessage(page, 30000, locators.length + 1); // return the response return await getLastAgentMessage(page); From 192815b2c9d0e0a1e47e4a28729387d02fcc9707 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 18:55:41 -0800 Subject: [PATCH 058/138] removed playwright debug flag --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 9c3679f41..285f9fa61 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -88,7 +88,7 @@ jobs: timeout-minutes: 180 # run: | # working-directory: ts/packages/shell - run: npx playwright test --debug + run: npx playwright test working-directory: ts/packages/shell - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} From fdaea970cd3af66b3696b9ae64567112b5726504 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 19:05:58 -0800 Subject: [PATCH 059/138] testing no content printing --- ts/packages/shell/test/testHelper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 8bbab2c33..2915f4141 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -41,9 +41,9 @@ export async function startShell(): Promise { const app: ElectronApplication = await electron.launch({ args: [getAppPath()] }); runningApplications.set(process.env["INSTANCE_NAME"]!, app); - app.on('window', async (data) => { - console.log(`New Window created! ${await data.content()}`); - }); + // app.on('window', async (data) => { + // console.log(`New Window created! ${await data.content()}`); + // }); // get the main window const mainWindow: Page = await getMainWindow(app); From a81c8ef65f4636e57dc8f3fd054204c6326faf5d Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 19:17:23 -0800 Subject: [PATCH 060/138] small refactor --- ts/packages/shell/test/testHelper.ts | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 2915f4141..51c58e617 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -41,10 +41,6 @@ export async function startShell(): Promise { const app: ElectronApplication = await electron.launch({ args: [getAppPath()] }); runningApplications.set(process.env["INSTANCE_NAME"]!, app); - // app.on('window', async (data) => { - // console.log(`New Window created! ${await data.content()}`); - // }); - // get the main window const mainWindow: Page = await getMainWindow(app); @@ -117,25 +113,11 @@ export function getAppPath(): string { */ export async function sendUserRequest(prompt: string, page: Page) { - try { - const locator: Locator = page.locator("#phraseDiv"); - //const locator: Locator = await page.locator(".user-textarea"); - await locator.waitFor({ timeout: 30000, state: "visible" }); - await locator.focus({ timeout: 30000 }); - await locator.fill(prompt, { timeout: 30000 }); - await locator.press("Enter", { timeout: 30000 }); - - return; - } catch (e) { - // // TODO: find alternate method when the above fails. - // console.log(e); - - // let title = await page.title(); - // console.log(title); - - // const c = await page.content(); - // console.log(c); - } + const locator: Locator = page.locator("#phraseDiv"); + await locator.waitFor({ timeout: 30000, state: "visible" }); + await locator.focus({ timeout: 30000 }); + await locator.fill(prompt, { timeout: 30000 }); + await locator.press("Enter", { timeout: 30000 }); } /** From 8384e0b414cf9083696e560ab09acb5fb9857862 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 19:18:39 -0800 Subject: [PATCH 061/138] trying ubuntu and macos again --- .github/workflows/playwright.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 285f9fa61..83e3e7aaf 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -22,8 +22,8 @@ jobs: strategy: fail-fast: false matrix: - # os: ["ubuntu-latest", "windows-latest", "macos-latest"] - os: ["windows-latest"] + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + #os: ["windows-latest"] version: [18, 20, 22] exclude: # TODO: Test timeout jest work for some reason From b546685941ee330c6088ef663636178138b32aaa Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 19:32:41 -0800 Subject: [PATCH 062/138] testing mac build --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 83e3e7aaf..ccb294b6f 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -65,7 +65,7 @@ jobs: - name: Build working-directory: ts run: | - npm run build + npm run build:mac # - name: Test # working-directory: ts # run: | From 86160ac81b0435079a39c0ab29d34fbd031a2082 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 19:36:06 -0800 Subject: [PATCH 063/138] macos build --- .github/workflows/playwright.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index ccb294b6f..a482a1e9b 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -65,7 +65,11 @@ jobs: - name: Build working-directory: ts run: | - npm run build:mac + npm run build + - name: Build for Mac + working-directory: ts/packages/shell + run: | + npm run build:win # - name: Test # working-directory: ts # run: | From fe06eeedea83cc91d96f0b7f8e0de086c27f9d67 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 19:45:48 -0800 Subject: [PATCH 064/138] fixed mac build, (not windows) --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index a482a1e9b..e49a4fdea 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -69,7 +69,7 @@ jobs: - name: Build for Mac working-directory: ts/packages/shell run: | - npm run build:win + npm run build:mac # - name: Test # working-directory: ts # run: | From ac20c94799578f571565aaf1092eef26de040dc5 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 19:51:57 -0800 Subject: [PATCH 065/138] added build statements for each OS flavor --- .github/workflows/playwright.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index e49a4fdea..e8be69947 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -66,11 +66,28 @@ jobs: working-directory: ts run: | npm run build + + - name: Build for Mac + if: matrix.os == 'macos-latest' working-directory: ts/packages/shell run: | npm run build:mac - # - name: Test + + - name: Build for Windows + if: matrix.os == 'windows-latest' + working-directory: ts/packages/shell + run: | + npm run build:win + + - name: Build for Linux + if: matrix.os == 'ubuntu-latest' + working-directory: ts/packages/shell + run: | + npm run build:linux + + + # - name: Test # working-directory: ts # run: | # npm run test From 68fefb7b790253ad382cad658ecd6cbd15883c40 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 20:05:36 -0800 Subject: [PATCH 066/138] build shell without matrix --- .github/workflows/playwright.yml | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index e8be69947..ef1773c75 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -62,32 +62,17 @@ jobs: - name: Install Playwright Browsers run: pnpm exec playwright install --with-deps working-directory: ts/packages/shell - - name: Build + - name: Build repo working-directory: ts run: | npm run build - - - name: Build for Mac - if: matrix.os == 'macos-latest' + - name: Build Shell working-directory: ts/packages/shell run: | - npm run build:mac - - - name: Build for Windows - if: matrix.os == 'windows-latest' - working-directory: ts/packages/shell - run: | - npm run build:win - - - name: Build for Linux - if: matrix.os == 'ubuntu-latest' - working-directory: ts/packages/shell - run: | - npm run build:linux - + npm run build - # - name: Test + # - name: Test # working-directory: ts # run: | # npm run test From 57af11d27f1a4ff91bdb223b32cfc0dee2f8ed3f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 20:11:45 -0800 Subject: [PATCH 067/138] removed mac & linux --- .github/workflows/playwright.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index ef1773c75..bcbced42c 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -22,8 +22,8 @@ jobs: strategy: fail-fast: false matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest"] - #os: ["windows-latest"] + #os: ["ubuntu-latest", "windows-latest", "macos-latest"] + os: ["windows-latest"] version: [18, 20, 22] exclude: # TODO: Test timeout jest work for some reason From 107f827ee7e34db9ed3443538ffe539fd046f3d6 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 20:12:34 -0800 Subject: [PATCH 068/138] test running shell --- .github/workflows/playwright.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index bcbced42c..10d516e22 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -89,7 +89,11 @@ jobs: - name: Test CLI - verify .env & endpoint connectivity run: | npm run start:dev 'prompt' 'why is the sky blue' - working-directory: ts/packages/cli + working-directory: ts/packages/cli + - name: Run shell + run: | + npm run shell + working-directory: ts/packages/cli - name: Shell Tests timeout-minutes: 180 # run: | From 2af92f734687a1e44c640d1e9a54b57ae1ec4d4d Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 20:20:31 -0800 Subject: [PATCH 069/138] removing shell build --- .github/workflows/playwright.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 10d516e22..c6e446693 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -67,10 +67,10 @@ jobs: run: | npm run build - - name: Build Shell - working-directory: ts/packages/shell - run: | - npm run build + # - name: Build Shell + # working-directory: ts/packages/shell + # run: | + # npm run build # - name: Test # working-directory: ts @@ -92,8 +92,8 @@ jobs: working-directory: ts/packages/cli - name: Run shell run: | - npm run shell - working-directory: ts/packages/cli + npm start + working-directory: ts/packages/shell - name: Shell Tests timeout-minutes: 180 # run: | From f199198d365abf2c5376a21449266a560ea66664 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 20:27:37 -0800 Subject: [PATCH 070/138] added back linux macos --- .github/workflows/playwright.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index c6e446693..dfb61df2c 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -22,8 +22,8 @@ jobs: strategy: fail-fast: false matrix: - #os: ["ubuntu-latest", "windows-latest", "macos-latest"] - os: ["windows-latest"] + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + #os: ["windows-latest"] version: [18, 20, 22] exclude: # TODO: Test timeout jest work for some reason From f4177c22a8210bc0add0362d6fe3ac72896f6314 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 20:38:37 -0800 Subject: [PATCH 071/138] added perms for linux --- .github/workflows/playwright.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index dfb61df2c..9a1a566ef 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -90,11 +90,23 @@ jobs: run: | npm run start:dev 'prompt' 'why is the sky blue' working-directory: ts/packages/cli + + + - name: Build for Linux + if: matrix.os == 'ubuntu-latest' + working-directory: ts/packages/shell + run: | + sudo chown root /home/runner/work/TypeAgent/TypeAgent/ts/node_modules/.pnpm/electron@30.0.1/node_modules/electron/dist/chrome-sandbox + sudo chmod 4755 /home/runner/work/TypeAgent/TypeAgent/ts/node_modules/.pnpm/electron@30.0.1/node_modules/electron/dist/chrome-sandbox + - name: Run shell run: | npm start working-directory: ts/packages/shell - - name: Shell Tests + + + + - name: Shell Tests timeout-minutes: 180 # run: | # working-directory: ts/packages/shell From c53277effa3b40c6e522ea74b7fa8dad9641a683 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 20:39:19 -0800 Subject: [PATCH 072/138] fixed error --- .github/workflows/playwright.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 9a1a566ef..5243755bb 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -104,9 +104,7 @@ jobs: npm start working-directory: ts/packages/shell - - - - name: Shell Tests + - name: Shell Tests timeout-minutes: 180 # run: | # working-directory: ts/packages/shell From a52328988a1d3f28388c9c2a3f9a48f5a35ed62d Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 20:45:25 -0800 Subject: [PATCH 073/138] again disabled linux & ubuntu --- .github/workflows/playwright.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 5243755bb..2bae1db54 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -92,17 +92,17 @@ jobs: working-directory: ts/packages/cli - - name: Build for Linux - if: matrix.os == 'ubuntu-latest' - working-directory: ts/packages/shell - run: | - sudo chown root /home/runner/work/TypeAgent/TypeAgent/ts/node_modules/.pnpm/electron@30.0.1/node_modules/electron/dist/chrome-sandbox - sudo chmod 4755 /home/runner/work/TypeAgent/TypeAgent/ts/node_modules/.pnpm/electron@30.0.1/node_modules/electron/dist/chrome-sandbox + # - name: Build for Linux + # if: matrix.os == 'ubuntu-latest' + # working-directory: ts/packages/shell + # run: | + # sudo chown root /home/runner/work/TypeAgent/TypeAgent/ts/node_modules/.pnpm/electron@30.0.1/node_modules/electron/dist/chrome-sandbox + # sudo chmod 4755 /home/runner/work/TypeAgent/TypeAgent/ts/node_modules/.pnpm/electron@30.0.1/node_modules/electron/dist/chrome-sandbox - - name: Run shell - run: | - npm start - working-directory: ts/packages/shell + # - name: Run shell + # run: | + # npm start + # working-directory: ts/packages/shell - name: Shell Tests timeout-minutes: 180 From 6a4b4c5c677582df3523290a3ff1cf0292e29e38 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 26 Jan 2025 20:46:22 -0800 Subject: [PATCH 074/138] disabled matrix --- .github/workflows/playwright.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 2bae1db54..41576179a 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -22,8 +22,8 @@ jobs: strategy: fail-fast: false matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest"] - #os: ["windows-latest"] + #os: ["ubuntu-latest", "windows-latest", "macos-latest"] + os: ["windows-latest"] version: [18, 20, 22] exclude: # TODO: Test timeout jest work for some reason From b3614fed28c13306ed27daf982359367de0261de Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 11:06:18 -0800 Subject: [PATCH 075/138] trying private keyvault --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 41576179a..50e45ca28 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -84,7 +84,7 @@ jobs: subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} - name: Get Keys run: | - node tools/scripts/getKeys.mjs + node tools/scripts/getKeys.mjs --private build-pipeline-kv working-directory: ts - name: Test CLI - verify .env & endpoint connectivity run: | From c26bc8481ee391dfbd500fb4abe592ec62682fd3 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 11:07:38 -0800 Subject: [PATCH 076/138] renamed --- .github/workflows/{playwright.yml => shell-tests.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{playwright.yml => shell-tests.yml} (100%) diff --git a/.github/workflows/playwright.yml b/.github/workflows/shell-tests.yml similarity index 100% rename from .github/workflows/playwright.yml rename to .github/workflows/shell-tests.yml From 5a3a8f84dde21b44cac7b5cd7716a0a3b2cb000d Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 11:27:30 -0800 Subject: [PATCH 077/138] changed vault --- .github/workflows/shell-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 50e45ca28..8805a05a1 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -84,7 +84,7 @@ jobs: subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} - name: Get Keys run: | - node tools/scripts/getKeys.mjs --private build-pipeline-kv + node tools/scripts/getKeys.mjs --vault build-pipeline-kv working-directory: ts - name: Test CLI - verify .env & endpoint connectivity run: | From 4328723e24ba07b2146826b0a624a5bf979a0337 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 11:59:46 -0800 Subject: [PATCH 078/138] Added getkeys debug launch configs --- ts/.vscode/launch.json | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/ts/.vscode/launch.json b/ts/.vscode/launch.json index f72e1eb19..eaf43cce5 100644 --- a/ts/.vscode/launch.json +++ b/ts/.vscode/launch.json @@ -339,7 +339,39 @@ "env": { "REMOTE_DEBUGGING_PORT": "9222" } - }, + }, + { + "type": "node", + "request": "launch", + "name": "Launch getKeys (shared key vault)", + "skipFiles": [ + "/**" + ], + "cwd": "${workspaceFolder}", + "program": "tools/scripts/getKeys.mjs", + "args": [ ], + "console": "externalTerminal", + //"preLaunchTask": "pnpm: build", + "outFiles": [ + "${workspaceFolder}/**/*.js" + ] + }, + { + "type": "node", + "request": "launch", + "name": "Launch getKeys (private key vault)", + "skipFiles": [ + "/**" + ], + "cwd": "${workspaceFolder}", + "program": "tools/scripts/getKeys.mjs", + "args": [ "--vault", "build-pipeline-kv" ], + "console": "externalTerminal", + //"preLaunchTask": "pnpm: build", + "outFiles": [ + "${workspaceFolder}/**/*.js" + ] + }, ] } From 24361ecd8f9b1e0f43a4385ef8cc223b9dd417ed Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 19:54:21 -0800 Subject: [PATCH 079/138] setup shell simple tests for root package file --- ts/package.json | 1 + ts/packages/shell/test/simple.spec.ts | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ts/package.json b/ts/package.json index 9bd874d6c..4a5159c17 100644 --- a/ts/package.json +++ b/ts/package.json @@ -33,6 +33,7 @@ "regen": "pnpm -C packages/cli run regen", "regen:builtin": "pnpm -C packages/cli run regen:builtin", "shell": "pnpm -C packages/shell run dev", + "shell:test": "npx --prefix packages/shell playwright test simple.spec.ts", "test": "pnpm -r --no-sort --stream --workspace-concurrency=0 run test", "test:full": "pnpm run -r --parallel --stream test:full" }, diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts index beafa690a..0e7304564 100644 --- a/ts/packages/shell/test/simple.spec.ts +++ b/ts/packages/shell/test/simple.spec.ts @@ -1,16 +1,31 @@ import test, { ElectronApplication,Page,_electron, - _electron as electron, } from "@playwright/test"; -import { getAppPath, startShell } from "./testHelper"; + _electron as electron, + expect, } from "@playwright/test"; +import { exitApplication, getAppPath, sendUserRequestAndWaitForResponse, startShell } from "./testHelper"; test("dummy", async () => { // do nothing }); -test("simple", async ({}, testInfo) => { +test("simple", { tag: '@smoke' }, async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); const app: ElectronApplication = await electron.launch({ args: [getAppPath()] }); const mainWindow: Page = await app.firstWindow(); await mainWindow.bringToFront(); await app.close(); +}); + +test.skip("why is the sky blue?", { tag: '@smoke' }, async ({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); + + // launch the app + const mainWindow: Page = await startShell(); + + const msg = await sendUserRequestAndWaitForResponse(`why is the sky blue?`, mainWindow); + + expect(msg.toLowerCase(), "Chat agent didn't respond with the expected message.").toContain("raleigh scattering.") + + // close the application + await exitApplication(mainWindow); }); \ No newline at end of file From 7fd1712e6c1b95f433cbae4e901439f18fdf8728 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 20:02:07 -0800 Subject: [PATCH 080/138] delete env file after tests --- .github/workflows/shell-tests.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 8805a05a1..b05c49e8b 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -name: Shell Tests +name: Shell Tests - Full Suite on: push: branches: [ dev/robgruen/electron_tests ] @@ -108,8 +108,12 @@ jobs: timeout-minutes: 180 # run: | # working-directory: ts/packages/shell - run: npx playwright test + run: | + npx playwright test + rm ../../.env + working-directory: ts/packages/shell + - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: From d7aee6a8e927fb39325fcfcf00b2b6a5eda0eecd Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 20:26:40 -0800 Subject: [PATCH 081/138] Added shell & cli smoke tests to build --- .github/workflows/build-ts.yml | 21 +++++++++++++++++ .github/workflows/shell-tests.yml | 38 ++----------------------------- 2 files changed, 23 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index 409f783ea..8b87ae3fa 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -84,3 +84,24 @@ jobs: working-directory: ts run: | npm run lint + - name: Login to Azure + uses: azure/login@v2.2.0 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} + - name: Get Keys + run: | + node tools/scripts/getKeys.mjs --vault build-pipeline-kv + working-directory: ts + - name: Test CLI - smoke + run: | + npm run start:dev 'prompt' 'why is the sky blue' + working-directory: ts/packages/cli + - name: Shell Tests - smoke + timeout-minutes: 60 + run: | + npx playwright test simple.spec.ts + rm ../../.env + working-directory: ts/packages/shell + diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index b05c49e8b..0a645d51b 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -66,16 +66,6 @@ jobs: working-directory: ts run: | npm run build - - # - name: Build Shell - # working-directory: ts/packages/shell - # run: | - # npm run build - - # - name: Test - # working-directory: ts - # run: | - # npm run test - name: Login to Azure uses: azure/login@v2.2.0 with: @@ -89,31 +79,13 @@ jobs: - name: Test CLI - verify .env & endpoint connectivity run: | npm run start:dev 'prompt' 'why is the sky blue' - working-directory: ts/packages/cli - - - # - name: Build for Linux - # if: matrix.os == 'ubuntu-latest' - # working-directory: ts/packages/shell - # run: | - # sudo chown root /home/runner/work/TypeAgent/TypeAgent/ts/node_modules/.pnpm/electron@30.0.1/node_modules/electron/dist/chrome-sandbox - # sudo chmod 4755 /home/runner/work/TypeAgent/TypeAgent/ts/node_modules/.pnpm/electron@30.0.1/node_modules/electron/dist/chrome-sandbox - - # - name: Run shell - # run: | - # npm start - # working-directory: ts/packages/shell - + working-directory: ts/packages/cli - name: Shell Tests - timeout-minutes: 180 - # run: | - # working-directory: ts/packages/shell + timeout-minutes: 60 run: | npx playwright test rm ../../.env - working-directory: ts/packages/shell - - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: @@ -128,9 +100,3 @@ jobs: path: ts/packages/shell/playwright-report/ overwrite: true retention-days: 30 - - - # - name: Lint - # working-directory: ts - # run: | - # npm run lint From 63b2e3e769aa01aa1ed45e299b802390612b1eac Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 20:28:18 -0800 Subject: [PATCH 082/138] updated shell smoke test for windows only --- .github/workflows/build-ts.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index 8b87ae3fa..4de0525cf 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -99,6 +99,7 @@ jobs: npm run start:dev 'prompt' 'why is the sky blue' working-directory: ts/packages/cli - name: Shell Tests - smoke + if: {{ matrix.os }} == "windows-latest" timeout-minutes: 60 run: | npx playwright test simple.spec.ts From d5f1e8f0233f57f3eeeeb998bc7a6bcb148bb691 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 20:37:16 -0800 Subject: [PATCH 083/138] added continue-on-error --- .github/workflows/build-ts.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index 4de0525cf..7dd7fa210 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -97,7 +97,8 @@ jobs: - name: Test CLI - smoke run: | npm run start:dev 'prompt' 'why is the sky blue' - working-directory: ts/packages/cli + working-directory: ts/packages/cli + continue-on-error: true - name: Shell Tests - smoke if: {{ matrix.os }} == "windows-latest" timeout-minutes: 60 @@ -105,4 +106,5 @@ jobs: npx playwright test simple.spec.ts rm ../../.env working-directory: ts/packages/shell + continue-on-error: true From c0c10f2ec4b6525d1afd7347c6e944df7bcfc70a Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 20:37:40 -0800 Subject: [PATCH 084/138] added continue-on-error --- .github/workflows/shell-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 0a645d51b..aff59070c 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -31,6 +31,7 @@ jobs: version: 22 runs-on: ${{ matrix.os }} + continue-on-error: true steps: - if: runner.os == 'Linux' run: | From 3599a358574f12a68ec4bf224476344ef2d1827d Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 20:41:53 -0800 Subject: [PATCH 085/138] added playwright dependency --- .github/workflows/build-ts.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index 7dd7fa210..2b4054312 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -69,6 +69,9 @@ jobs: working-directory: ts run: | pnpm install --frozen-lockfile --strict-peer-dependencies + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + working-directory: ts/packages/shell - name: Build if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} working-directory: ts From 10a9de0f25a1205b2501892995c094595bae7412 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 20:46:27 -0800 Subject: [PATCH 086/138] changed full shell tests to schedule based workflow --- .github/workflows/shell-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index aff59070c..a0c5b33f7 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -4,9 +4,9 @@ name: Shell Tests - Full Suite on: push: - branches: [ dev/robgruen/electron_tests ] - # schedule: - # - cron: "0 0 * * *" + branches: [ "main", "dev/robgruen/electron_tests" ] + schedule: + - cron: "0 0 * * *" #start of every day permissions: id-token: write From 66559f2a3c41efba89468ac2c12b235606d3d34f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 20:53:12 -0800 Subject: [PATCH 087/138] moved cleanup step to the end --- .github/workflows/build-ts.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index 1a2f3d444..34dbf0d13 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -101,9 +101,10 @@ jobs: - name: Shell Tests - smoke if: {{ matrix.os }} == "windows-latest" timeout-minutes: 60 - run: | - npx playwright test simple.spec.ts - rm ../../.env + run: npx playwright test simple.spec.ts working-directory: ts/packages/shell continue-on-error: true + - name: Cleanup ENV file + run: rm .env + From 13014db29d5131e125ece781aef6e8fa400a7571 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 20:55:27 -0800 Subject: [PATCH 088/138] added missing copywrite --- ts/packages/shell/test/simple.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts index 0e7304564..c9dcec2ee 100644 --- a/ts/packages/shell/test/simple.spec.ts +++ b/ts/packages/shell/test/simple.spec.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + import test, { ElectronApplication,Page,_electron, _electron as electron, expect, } from "@playwright/test"; From ea07a39ff93321f489963cca826fbd94f7520673 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 20:59:03 -0800 Subject: [PATCH 089/138] remove continue on fail --- .github/workflows/build-ts.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index 34dbf0d13..b751a9bb0 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -97,7 +97,6 @@ jobs: run: | npm run start:dev 'prompt' 'why is the sky blue' working-directory: ts/packages/cli - continue-on-error: true - name: Shell Tests - smoke if: {{ matrix.os }} == "windows-latest" timeout-minutes: 60 From 0611eaa241e9aaf29a2ef2b06da054668402ad90 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 21:01:36 -0800 Subject: [PATCH 090/138] fixed yml error --- .github/workflows/build-ts.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index b751a9bb0..8e69799be 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -32,6 +32,10 @@ jobs: matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] version: [18, 20, 22] + exclude: + # TODO: Test timeout jest work for some reason + - os: windows-latest + version: 22 runs-on: ${{ matrix.os }} steps: @@ -97,13 +101,13 @@ jobs: run: | npm run start:dev 'prompt' 'why is the sky blue' working-directory: ts/packages/cli + continue-on-error: true - name: Shell Tests - smoke - if: {{ matrix.os }} == "windows-latest" + if: ${{ matrix.os }} == "windows-latest" timeout-minutes: 60 - run: npx playwright test simple.spec.ts + run: | + npx playwright test simple.spec.ts + rm ../../.env working-directory: ts/packages/shell continue-on-error: true - - name: Cleanup ENV file - run: rm .env - From 1726bba9123f5b3e0929fd4ccce85808c76b3393 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 21:12:05 -0800 Subject: [PATCH 091/138] renamed test to shell:test --- ts/packages/shell/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/packages/shell/package.json b/ts/packages/shell/package.json index 9ed31d80f..daad0b414 100644 --- a/ts/packages/shell/package.json +++ b/ts/packages/shell/package.json @@ -24,7 +24,7 @@ "prettier": "prettier --check . --ignore-path ../../.prettierignore", "prettier:fix": "prettier --write . --ignore-path ../../.prettierignore", "start": "electron-vite preview", - "test": "npx playwright test", + "shell:test": "npx playwright test", "typecheck": "concurrently npm:typecheck:node npm:typecheck:web", "typecheck:node": "tsc -p tsconfig.node.json", "typecheck:web": "tsc -p tsconfig.web.json" From 0b65f6f3bf851ed0a4afee0117c6ce22e476e050 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 21:14:36 -0800 Subject: [PATCH 092/138] sorted packages --- ts/packages/shell/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/packages/shell/package.json b/ts/packages/shell/package.json index daad0b414..e85539ada 100644 --- a/ts/packages/shell/package.json +++ b/ts/packages/shell/package.json @@ -23,8 +23,8 @@ "postinstall": "cross-env \"npm_execpath=\" electron-builder install-app-deps", "prettier": "prettier --check . --ignore-path ../../.prettierignore", "prettier:fix": "prettier --write . --ignore-path ../../.prettierignore", - "start": "electron-vite preview", "shell:test": "npx playwright test", + "start": "electron-vite preview", "typecheck": "concurrently npm:typecheck:node npm:typecheck:web", "typecheck:node": "tsc -p tsconfig.node.json", "typecheck:web": "tsc -p tsconfig.web.json" From 2a4f32923e6086072bdb8ce20d32be259d15d0b1 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 21:28:04 -0800 Subject: [PATCH 093/138] ran prettier --- .../system/handlers/envCommandHandler.ts | 22 +++- .../src/context/system/systemAgent.ts | 5 +- ts/packages/shell/playwright.config.ts | 10 +- ts/packages/shell/src/main/agent.ts | 4 +- ts/packages/shell/src/main/index.ts | 23 +++- ts/packages/shell/src/main/shellSettings.ts | 5 +- .../src/renderer/src/messageContainer.ts | 2 +- .../shell/src/renderer/src/setContent.ts | 6 +- ts/packages/shell/test/configCommands.spec.ts | 68 +++++++--- ts/packages/shell/test/global.setup.ts | 8 +- ts/packages/shell/test/global.teardown.ts | 8 +- ts/packages/shell/test/hostWindow.spec.ts | 49 ++++++-- .../shell/test/sessionCommands.spec.ts | 116 +++++++++++++----- ts/packages/shell/test/simple.spec.ts | 35 ++++-- ts/packages/shell/test/testHelper.ts | 102 +++++++++------ ts/tools/scripts/getKeys.mjs | 10 +- 16 files changed, 335 insertions(+), 138 deletions(-) diff --git a/ts/packages/dispatcher/src/context/system/handlers/envCommandHandler.ts b/ts/packages/dispatcher/src/context/system/handlers/envCommandHandler.ts index 30ac11a5d..79e9036ab 100644 --- a/ts/packages/dispatcher/src/context/system/handlers/envCommandHandler.ts +++ b/ts/packages/dispatcher/src/context/system/handlers/envCommandHandler.ts @@ -8,7 +8,10 @@ import { } from "@typeagent/agent-sdk/helpers/command"; import { CommandHandlerContext } from "../../commandHandlerContext.js"; import { ActionContext, ParsedCommandParams } from "@typeagent/agent-sdk"; -import { displayError, displayResult } from "@typeagent/agent-sdk/helpers/display"; +import { + displayError, + displayResult, +} from "@typeagent/agent-sdk/helpers/display"; import dotenv from "dotenv"; import { Action } from "agent-cache"; @@ -42,7 +45,8 @@ export class EnvCommandHandler implements CommandHandlerNoParams { } export class EnvVarCommandHandler implements CommandHandler { - public readonly description: string = "Echos the value of a named environment variable to the user interface"; + public readonly description: string = + "Echos the value of a named environment variable to the user interface"; public readonly parameters = { args: { name: { @@ -50,11 +54,17 @@ export class EnvVarCommandHandler implements CommandHandler { }, }, } as const; - public async run(context: ActionContext, params: ParsedCommandParams,) { + public async run( + context: ActionContext, + params: ParsedCommandParams, + ) { if (process.env[params.args.name]) { displayResult(process.env[params.args.name]!, context); } else { - displayError(`The environment variable ${params.args.name} does not exist.`, context); + displayError( + `The environment variable ${params.args.name} does not exist.`, + context, + ); } } } @@ -65,7 +75,7 @@ export function getEnvCommandHandlers(): CommandHandlerTable { defaultSubCommand: "all", commands: { all: new EnvCommandHandler(), - get: new EnvVarCommandHandler() - } + get: new EnvVarCommandHandler(), + }, }; } diff --git a/ts/packages/dispatcher/src/context/system/systemAgent.ts b/ts/packages/dispatcher/src/context/system/systemAgent.ts index 274a12c35..9bacc615c 100644 --- a/ts/packages/dispatcher/src/context/system/systemAgent.ts +++ b/ts/packages/dispatcher/src/context/system/systemAgent.ts @@ -61,7 +61,10 @@ import { getParameterNames, validateAction, } from "action-schema"; -import { EnvCommandHandler, getEnvCommandHandlers } from "./handlers/envCommandHandler.js"; +import { + EnvCommandHandler, + getEnvCommandHandlers, +} from "./handlers/envCommandHandler.js"; import { executeNotificationAction } from "./action/notificationActionHandler.js"; import { executeHistoryAction } from "./action/historyActionHandler.js"; diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts index e9117e250..4102afa60 100644 --- a/ts/packages/shell/playwright.config.ts +++ b/ts/packages/shell/playwright.config.ts @@ -23,8 +23,8 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ - workers: 1, // fails with timeouts with more than 2 workers. :( - //process.env.CI ? 1 : undefined, + workers: 1, // fails with timeouts with more than 2 workers. :( + //process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: "html", /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ @@ -35,7 +35,7 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", }, - + maxFailures: 0, timeout: 300_000, // Set global timeout to 120 seconds @@ -49,12 +49,12 @@ export default defineConfig({ { name: `global teardown`, testMatch: /global\.teardown\.ts/, - }, + }, { name: "chromium", use: { ...devices["Desktop Chrome"] }, fullyParallel: false, - dependencies: [ "global setup"], + dependencies: ["global setup"], //testMatch: /simple\.spec\.ts/, }, ], diff --git a/ts/packages/shell/src/main/agent.ts b/ts/packages/shell/src/main/agent.ts index 9cf96c8a2..0fb0e636f 100644 --- a/ts/packages/shell/src/main/agent.ts +++ b/ts/packages/shell/src/main/agent.ts @@ -106,7 +106,9 @@ class ShellSetSettingCommandHandler implements CommandHandler { try { agentContext.settings.set(name, value); } catch (e) { - throw new Error(`Unable to set ${key} to ${value}. Details: ${e}`); + throw new Error( + `Unable to set ${key} to ${value}. Details: ${e}`, + ); } } else { agentContext.settings.set(name, value); diff --git a/ts/packages/shell/src/main/index.ts b/ts/packages/shell/src/main/index.ts index 6898bd06c..b70de5742 100644 --- a/ts/packages/shell/src/main/index.ts +++ b/ts/packages/shell/src/main/index.ts @@ -229,16 +229,24 @@ function createWindow() { }); // Notify renderer process whenever settings are modified - ShellSettings.getinstance().onSettingsChanged = (settingName?: string | undefined): void => { + ShellSettings.getinstance().onSettingsChanged = ( + settingName?: string | undefined, + ): void => { chatView?.webContents.send( "settings-changed", ShellSettings.getinstance().getSerializable(), ); if (settingName == "size") { - mainWindow?.setSize(ShellSettings.getinstance().width, ShellSettings.getinstance().height); + mainWindow?.setSize( + ShellSettings.getinstance().width, + ShellSettings.getinstance().height, + ); } else if (settingName == "position") { - mainWindow?.setPosition(ShellSettings.getinstance().x!, ShellSettings.getinstance().y!); + mainWindow?.setPosition( + ShellSettings.getinstance().x!, + ShellSettings.getinstance().y!, + ); } if (settingName == "zoomLevel") { @@ -651,7 +659,11 @@ async function initialize() { // On windows, we will spin up a local end point that listens // for pen events which will trigger speech reco // Don't spin this up during testing - if (process.platform == "win32" && (process.env["INSTANCE_NAME"] == undefined || process.env["INSTANCE_NAME"].startsWith("test_") == false)) { + if ( + process.platform == "win32" && + (process.env["INSTANCE_NAME"] == undefined || + process.env["INSTANCE_NAME"].startsWith("test_") == false) + ) { const pipePath = path.join("\\\\.\\pipe\\TypeAgent", "speech"); const server = net.createServer((stream) => { stream.on("data", (c) => { @@ -734,9 +746,8 @@ function zoomOut(chatView: BrowserView) { } function setZoomLevel(zoomLevel: number, chatView: BrowserView | null) { - if (zoomLevel < 0.1) { - zoomLevel = 0.1 + zoomLevel = 0.1; } else if (zoomLevel > 10) { zoomLevel = 10; } diff --git a/ts/packages/shell/src/main/shellSettings.ts b/ts/packages/shell/src/main/shellSettings.ts index 83024a9db..5cd06f21c 100644 --- a/ts/packages/shell/src/main/shellSettings.ts +++ b/ts/packages/shell/src/main/shellSettings.ts @@ -158,7 +158,10 @@ export class ShellSettings } } - if (ShellSettings.getinstance().onSettingsChanged != null && oldValue != value) { + if ( + ShellSettings.getinstance().onSettingsChanged != null && + oldValue != value + ) { ShellSettings.getinstance().onSettingsChanged!(name); } } diff --git a/ts/packages/shell/src/renderer/src/messageContainer.ts b/ts/packages/shell/src/renderer/src/messageContainer.ts index 9a91ae8c2..a959e76ee 100644 --- a/ts/packages/shell/src/renderer/src/messageContainer.ts +++ b/ts/packages/shell/src/renderer/src/messageContainer.ts @@ -251,7 +251,7 @@ export class MessageContainer { this.classNameSuffix, appendMode === "inline" && this.lastAppendMode !== "inline" ? "block" - : appendMode, + : appendMode, ); this.speak(speakText, appendMode); diff --git a/ts/packages/shell/src/renderer/src/setContent.ts b/ts/packages/shell/src/renderer/src/setContent.ts index d93b3560a..7287b1c80 100644 --- a/ts/packages/shell/src/renderer/src/setContent.ts +++ b/ts/packages/shell/src/renderer/src/setContent.ts @@ -153,7 +153,11 @@ export function setContent( if (type === "text") { const prevElm = contentDiv.lastChild as HTMLElement | null; - if (prevElm?.classList.contains(`chat-message-${classNameModifier}-text`)) { + if ( + prevElm?.classList.contains( + `chat-message-${classNameModifier}-text`, + ) + ) { // If there is an existing text element then append to it. contentElm = prevElm; } else { diff --git a/ts/packages/shell/test/configCommands.spec.ts b/ts/packages/shell/test/configCommands.spec.ts index e5b88a9a4..3bc737f48 100644 --- a/ts/packages/shell/test/configCommands.spec.ts +++ b/ts/packages/shell/test/configCommands.spec.ts @@ -17,27 +17,44 @@ import { } from "./testHelper"; // Annotate entire file as serial. -test.describe.configure({ mode: 'serial' }); +test.describe.configure({ mode: "serial" }); test.describe("@config Commands", () => { - test("@config dev", async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); // launch the app const mainWindow: Page = await startShell(); - let msg = await sendUserRequestAndWaitForResponse(`@config dev`, mainWindow); + let msg = await sendUserRequestAndWaitForResponse( + `@config dev`, + mainWindow, + ); - expect(msg.toLowerCase(), "Dev mode was not turned on as expected.").toBe("development mode is enabled.") + expect( + msg.toLowerCase(), + "Dev mode was not turned on as expected.", + ).toBe("development mode is enabled."); - msg = await sendUserRequestAndWaitForResponse(`@config dev on`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@config dev on`, + mainWindow, + ); - expect(msg.toLowerCase(), "Dev mode was not turned on as expected.").toBe("development mode is enabled.") + expect( + msg.toLowerCase(), + "Dev mode was not turned on as expected.", + ).toBe("development mode is enabled."); - msg = await sendUserRequestAndWaitForResponse(`@config dev off`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@config dev off`, + mainWindow, + ); - expect(msg.toLowerCase(), "Dev mode was not turned off as expected.").toBe("development mode is disabled.") + expect( + msg.toLowerCase(), + "Dev mode was not turned off as expected.", + ).toBe("development mode is disabled."); // close the application await exitApplication(mainWindow); @@ -49,22 +66,39 @@ test.describe("@config Commands", () => { // launch the app const mainWindow: Page = await startShell(); - let msg = await sendUserRequestAndWaitForResponse(`@config schema oracle`, mainWindow); + let msg = await sendUserRequestAndWaitForResponse( + `@config schema oracle`, + mainWindow, + ); - expect(msg.toLowerCase(), "Oracle scheme should be ON but it is OFF.").toContain("✅"); + expect( + msg.toLowerCase(), + "Oracle scheme should be ON but it is OFF.", + ).toContain("✅"); - msg = await sendUserRequestAndWaitForResponse(`@config schema --off oracle`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@config schema --off oracle`, + mainWindow, + ); - expect(msg.toLowerCase(), "Oracle schema should be OFF but is is ON.").toContain("❌") + expect( + msg.toLowerCase(), + "Oracle schema should be OFF but is is ON.", + ).toContain("❌"); - msg = await sendUserRequestAndWaitForResponse(`@config dev off`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@config dev off`, + mainWindow, + ); - expect(msg.toLowerCase(), "Dev mode was not turned off as expected.").toBe("development mode is disabled.") + expect( + msg.toLowerCase(), + "Dev mode was not turned off as expected.", + ).toBe("development mode is disabled."); // close the application - await exitApplication(mainWindow); - + await exitApplication(mainWindow); }); }); -// TODO: Test action correction \ No newline at end of file +// TODO: Test action correction diff --git a/ts/packages/shell/test/global.setup.ts b/ts/packages/shell/test/global.setup.ts index 82b17ad6f..e4ad9e7bb 100644 --- a/ts/packages/shell/test/global.setup.ts +++ b/ts/packages/shell/test/global.setup.ts @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { test as setup } from '@playwright/test'; -import { deleteTestProfiles } from './testHelper'; +import { test as setup } from "@playwright/test"; +import { deleteTestProfiles } from "./testHelper"; // clear up old test profile data -setup('clear old test data', async ({ }) => { +setup("clear old test data", async ({}) => { deleteTestProfiles(); -}); \ No newline at end of file +}); diff --git a/ts/packages/shell/test/global.teardown.ts b/ts/packages/shell/test/global.teardown.ts index ff66ffe9a..d057c8190 100644 --- a/ts/packages/shell/test/global.teardown.ts +++ b/ts/packages/shell/test/global.teardown.ts @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { test as teardown } from '@playwright/test'; -import { deleteTestProfiles } from './testHelper'; +import { test as teardown } from "@playwright/test"; +import { deleteTestProfiles } from "./testHelper"; // clear up old test profile data -teardown('clear test data', async ({ }) => { +teardown("clear test data", async ({}) => { deleteTestProfiles(); -}); \ No newline at end of file +}); diff --git a/ts/packages/shell/test/hostWindow.spec.ts b/ts/packages/shell/test/hostWindow.spec.ts index ced811e29..0084bf274 100644 --- a/ts/packages/shell/test/hostWindow.spec.ts +++ b/ts/packages/shell/test/hostWindow.spec.ts @@ -20,10 +20,9 @@ import { import { exit } from "process"; // Annotate entire file as serial. -test.describe.configure({ mode: 'serial' }); +test.describe.configure({ mode: "serial" }); test.describe("Shell interface tests", () => { - /** * Test to ensure that the shell recall startup layout (position, size) */ @@ -50,7 +49,10 @@ test.describe("Shell interface tests", () => { const x: number = Math.ceil(Math.random() * 100); const y: number = Math.ceil(Math.random() * 100); - await sendUserRequestAndWaitForResponse(`@shell set position "[${x}, ${y}]"`, firstWindow); + await sendUserRequestAndWaitForResponse( + `@shell set position "[${x}, ${y}]"`, + firstWindow, + ); // close the application await exitApplication(firstWindow); @@ -59,7 +61,10 @@ test.describe("Shell interface tests", () => { const newWindow: Page = await startShell(); // get window size/position - const msg = await sendUserRequestAndWaitForResponse(`@shell show raw`, newWindow); + const msg = await sendUserRequestAndWaitForResponse( + `@shell show raw`, + newWindow, + ); // get the shell size and location from the raw settings const lines: string[] = msg.split("\n"); @@ -68,8 +73,14 @@ test.describe("Shell interface tests", () => { const newX: number = parseInt(lines[4].split(":")[1].trim()); const newY: number = parseInt(lines[5].split(":")[1].trim()); - expect(newHeight, `Window height mismatch! Expected ${height} got ${height}`).toBe(newHeight); - expect(newWidth, `Window width mismatch! Expected ${width} got ${width}`).toBe(newWidth); + expect( + newHeight, + `Window height mismatch! Expected ${height} got ${height}`, + ).toBe(newHeight); + expect( + newWidth, + `Window width mismatch! Expected ${width} got ${width}`, + ).toBe(newWidth); expect(newX, `X position mismatch! Expected ${x} got ${newX}`).toBe(x); expect(newY, `Y position mismatch!Expected ${y} got ${newY}`).toBe(y); @@ -101,16 +112,24 @@ test.describe("Shell interface tests", () => { async function testZoomLevel(level: number, page: Page) { // set the zoom level to 80% - await sendUserRequestAndWaitForResponse(`@shell set zoomLevel ${level}`, page); + await sendUserRequestAndWaitForResponse( + `@shell set zoomLevel ${level}`, + page, + ); // get the title let title = await page.title(); // get zoom level out of title let subTitle: string = title.match(/\d+%/)![0]; - let zoomLevel: number = parseInt(subTitle.substring(0, subTitle.length - 1)); + let zoomLevel: number = parseInt( + subTitle.substring(0, subTitle.length - 1), + ); - expect(zoomLevel, `Unexpected zoomLevel, expected ${level * 100}, got ${zoomLevel}`).toBe(level * 100); + expect( + zoomLevel, + `Unexpected zoomLevel, expected ${level * 100}, got ${zoomLevel}`, + ).toBe(level * 100); } /** @@ -118,7 +137,7 @@ test.describe("Shell interface tests", () => { */ test("send button state", async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); - + let agentMessageCount = 0; // start the app @@ -126,13 +145,19 @@ test.describe("Shell interface tests", () => { // make sure send button is disabled const sendButton = await mainWindow.locator("#sendbutton"); - await expect(sendButton, "Send button expected to be disabled.").toBeDisabled(); + await expect( + sendButton, + "Send button expected to be disabled.", + ).toBeDisabled(); // put some text in the text box const element = await mainWindow.waitForSelector("#phraseDiv"); await element.fill("This is a test..."); - await expect(sendButton, "Send button expected to be enabled.").toBeEnabled(); + await expect( + sendButton, + "Send button expected to be enabled.", + ).toBeEnabled(); // close the application await exitApplication(mainWindow); diff --git a/ts/packages/shell/test/sessionCommands.spec.ts b/ts/packages/shell/test/sessionCommands.spec.ts index 0cd3a1a9b..748ab73d2 100644 --- a/ts/packages/shell/test/sessionCommands.spec.ts +++ b/ts/packages/shell/test/sessionCommands.spec.ts @@ -19,10 +19,9 @@ import { import { session } from "electron"; // Annotate entire file as serial. -test.describe.configure({ mode: 'serial' }); +test.describe.configure({ mode: "serial" }); test.describe("@session Commands", () => { - test("@session new/list", async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); @@ -30,17 +29,28 @@ test.describe("@session Commands", () => { const mainWindow: Page = await startShell(); // get the session count - let msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); - + let msg = await sendUserRequestAndWaitForResponse( + `@session list`, + mainWindow, + ); + const sessions: string[] = msg.split("\n"); - - msg = await sendUserRequestAndWaitForResponse(`@session new`, mainWindow); + + msg = await sendUserRequestAndWaitForResponse( + `@session new`, + mainWindow, + ); expect(msg.toLowerCase()).toContain("new session created: "); - msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@session list`, + mainWindow, + ); const newSessions: string[] = msg.split("\n"); - expect(newSessions.length, "Session count mismatch!").toBe(sessions.length + 1); + expect(newSessions.length, "Session count mismatch!").toBe( + sessions.length + 1, + ); msg = await sendUserRequestAndWaitForResponse(`@history`, mainWindow); expect(msg.length, "History NOT cleared!").toBe(0); @@ -56,41 +66,66 @@ test.describe("@session Commands", () => { const mainWindow: Page = await startShell(); // create a new session so we have at least two - let msg = await sendUserRequestAndWaitForResponse(`@session new`, mainWindow); + let msg = await sendUserRequestAndWaitForResponse( + `@session new`, + mainWindow, + ); expect(msg.toLowerCase()).toContain("new session created: "); // get the session count - msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@session list`, + mainWindow, + ); let sessions = msg.split("\n"); const originalSessionCount: number = sessions.length; - const sessionName: string = sessions[sessions.length - 1]; - + const sessionName: string = sessions[sessions.length - 1]; + // issue delete session command - msg = await sendUserRequestAndWaitForResponse(`@session delete ${sessions[0]}`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@session delete ${sessions[0]}`, + mainWindow, + ); expect(msg.toLowerCase()).toContain("are you sure"); // click on cancel button await mainWindow.locator(".choice-button", { hasText: "No" }).click(); - + // verify session not deleted - msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@session list`, + mainWindow, + ); sessions = msg.split("\n"); - expect(sessions.length, "Session accidentally deleted.").toBe(originalSessionCount); + expect(sessions.length, "Session accidentally deleted.").toBe( + originalSessionCount, + ); // reissue delete session command - msg = await sendUserRequestAndWaitForResponse(`@session delete ${sessions[0]}`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@session delete ${sessions[0]}`, + mainWindow, + ); expect(msg.toLowerCase()).toContain("are you sure"); - + // click on Yes button await mainWindow.locator(".choice-button", { hasText: "Yes" }).click(); // get new session count - msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@session list`, + mainWindow, + ); sessions = msg.split("\n"); - expect(sessions.length, "Session accidentally deleted.").toBe(originalSessionCount - 1); + expect(sessions.length, "Session accidentally deleted.").toBe( + originalSessionCount - 1, + ); // get session info - msg = await sendUserRequestAndWaitForResponse(`@session info`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@session info`, + mainWindow, + ); sessions = msg.split("\n"); expect(sessions[1], "Wrong session selected.").toContain(sessionName); @@ -104,14 +139,20 @@ test.describe("@session Commands", () => { // launch the app const mainWindow: Page = await startShell(); - // reset - let msg = await sendUserRequestAndWaitForResponse(`@session reset`, mainWindow); + // reset + let msg = await sendUserRequestAndWaitForResponse( + `@session reset`, + mainWindow, + ); expect(msg).toContain("Session settings revert to default."); // issue clear session command - msg = await sendUserRequestAndWaitForResponse(`@session clear`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@session clear`, + mainWindow, + ); expect(msg.toLowerCase()).toContain("are you sure"); - + // click on Yes button await mainWindow.locator(".choice-button", { hasText: "Yes" }).click(); @@ -119,27 +160,38 @@ test.describe("@session Commands", () => { await exitApplication(mainWindow); }); - test("@session open", async({}, testInfo) => { + test("@session open", async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); // launch the app const mainWindow: Page = await startShell(); - // create a new session - let msg = await sendUserRequestAndWaitForResponse(`@session new`, mainWindow); + // create a new session + let msg = await sendUserRequestAndWaitForResponse( + `@session new`, + mainWindow, + ); expect(msg.toLowerCase()).toContain("new session created: "); // get the session list - msg = await sendUserRequestAndWaitForResponse(`@session list`, mainWindow); + msg = await sendUserRequestAndWaitForResponse( + `@session list`, + mainWindow, + ); const sessions: string[] = msg.split("\n"); // open the earlier session - msg = await sendUserRequestAndWaitForResponse(`@session open ${sessions[0]}`, mainWindow); - expect(msg, `Unexpected session openend!`).toBe(`Session opened: ${sessions[0]}`); + msg = await sendUserRequestAndWaitForResponse( + `@session open ${sessions[0]}`, + mainWindow, + ); + expect(msg, `Unexpected session openend!`).toBe( + `Session opened: ${sessions[0]}`, + ); // close the application await exitApplication(mainWindow); }); }); -// TODO: Test action correction \ No newline at end of file +// TODO: Test action correction diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts index c9dcec2ee..4ae374909 100644 --- a/ts/packages/shell/test/simple.spec.ts +++ b/ts/packages/shell/test/simple.spec.ts @@ -1,34 +1,51 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import test, { ElectronApplication,Page,_electron, +import test, { + ElectronApplication, + Page, + _electron, _electron as electron, - expect, } from "@playwright/test"; -import { exitApplication, getAppPath, sendUserRequestAndWaitForResponse, startShell } from "./testHelper"; + expect, +} from "@playwright/test"; +import { + exitApplication, + getAppPath, + sendUserRequestAndWaitForResponse, + startShell, +} from "./testHelper"; test("dummy", async () => { // do nothing }); -test("simple", { tag: '@smoke' }, async ({}, testInfo) => { +test("simple", { tag: "@smoke" }, async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); - const app: ElectronApplication = await electron.launch({ args: [getAppPath()] }); + const app: ElectronApplication = await electron.launch({ + args: [getAppPath()], + }); const mainWindow: Page = await app.firstWindow(); await mainWindow.bringToFront(); await app.close(); }); -test.skip("why is the sky blue?", { tag: '@smoke' }, async ({}, testInfo) => { +test.skip("why is the sky blue?", { tag: "@smoke" }, async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); // launch the app const mainWindow: Page = await startShell(); - const msg = await sendUserRequestAndWaitForResponse(`why is the sky blue?`, mainWindow); + const msg = await sendUserRequestAndWaitForResponse( + `why is the sky blue?`, + mainWindow, + ); - expect(msg.toLowerCase(), "Chat agent didn't respond with the expected message.").toContain("raleigh scattering.") + expect( + msg.toLowerCase(), + "Chat agent didn't respond with the expected message.", + ).toContain("raleigh scattering."); // close the application await exitApplication(mainWindow); -}); \ No newline at end of file +}); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 51c58e617..e66e84c12 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -1,67 +1,85 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { +import { _electron, _electron as electron, - ElectronApplication, - Locator, - Page, - TestDetails} from "@playwright/test"; + ElectronApplication, + Locator, + Page, + TestDetails, +} from "@playwright/test"; import { profile } from "node:console"; import fs from "node:fs"; import path from "node:path"; import os from "node:os"; -const runningApplications: Map = new Map(); +const runningApplications: Map = new Map< + string, + ElectronApplication +>(); /** * Starts the electron app and returns the main page after the greeting agent message has been posted. */ export async function startShell(): Promise { - // this is needed to isolate these tests session from other concurrently running tests - process.env["INSTANCE_NAME"] = `test_${process.env["TEST_WORKER_INDEX"]}_${process.env["TEST_PARALLEL_INDEX"]}`; + process.env["INSTANCE_NAME"] = + `test_${process.env["TEST_WORKER_INDEX"]}_${process.env["TEST_PARALLEL_INDEX"]}`; // other related multi-instance varibles that need to be modfied to ensure we can run multiple shell instances - process.env["PORT"] = (9001 + parseInt(process.env["TEST_WORKER_INDEX"]!)).toString(); - process.env["WEBSOCKET_HOST"] = `ws://localhost:${(8080 + parseInt(process.env["TEST_WORKER_INDEX"]!))}`; + process.env["PORT"] = ( + 9001 + parseInt(process.env["TEST_WORKER_INDEX"]!) + ).toString(); + process.env["WEBSOCKET_HOST"] = + `ws://localhost:${8080 + parseInt(process.env["TEST_WORKER_INDEX"]!)}`; // we may have to retry restarting the application due to session file locks or other such startup failures let retryAttempt = 0; const maxRetries = 10; do { - try { + try { if (runningApplications.has(process.env["INSTANCE_NAME"]!)) { - throw new Error("Application instance already running. Did you shutdown cleanly?"); + throw new Error( + "Application instance already running. Did you shutdown cleanly?", + ); } - console.log(`Starting electron instance '${process.env["INSTANCE_NAME"]}'`); - const app: ElectronApplication = await electron.launch({ args: [getAppPath()] }); + console.log( + `Starting electron instance '${process.env["INSTANCE_NAME"]}'`, + ); + const app: ElectronApplication = await electron.launch({ + args: [getAppPath()], + }); runningApplications.set(process.env["INSTANCE_NAME"]!, app); - // get the main window + // get the main window const mainWindow: Page = await getMainWindow(app); // wait for agent greeting await waitForAgentMessage(mainWindow, 30000, 1); return mainWindow; - - } catch (e) { - console.warn(`Unable to start electrom application (${process.env["INSTANCE_NAME"]}). Attempt ${retryAttempt} of ${maxRetries}`); + } catch (e) { + console.warn( + `Unable to start electrom application (${process.env["INSTANCE_NAME"]}). Attempt ${retryAttempt} of ${maxRetries}`, + ); retryAttempt++; if (runningApplications.get(process.env["INSTANCE_NAME"])) { - await runningApplications.get(process.env["INSTANCE_NAME"]!)!.close(); + await runningApplications + .get(process.env["INSTANCE_NAME"]!)! + .close(); } runningApplications.delete(process.env["INSTANCE_NAME"]!); } } while (retryAttempt <= maxRetries); - throw new Error(`Failed to start electrom app after ${maxRetries} attemps.`); + throw new Error( + `Failed to start electrom app after ${maxRetries} attemps.`, + ); } async function getMainWindow(app: ElectronApplication): Promise { @@ -90,7 +108,7 @@ export async function exitApplication(page: Page): Promise { await sendUserRequestFast("@exit", page); await runningApplications.get(process.env["INSTANCE_NAME"]!)!.close(); - + runningApplications.delete(process.env["INSTANCE_NAME"]!); } @@ -112,7 +130,6 @@ export function getAppPath(): string { * @param page The maing page from the electron host application. */ export async function sendUserRequest(prompt: string, page: Page) { - const locator: Locator = page.locator("#phraseDiv"); await locator.waitFor({ timeout: 30000, state: "visible" }); await locator.focus({ timeout: 30000 }); @@ -125,7 +142,7 @@ export async function sendUserRequest(prompt: string, page: Page) { * @param prompt The user request/prompt. * @param page The maing page from the electron host application. */ -export async function sendUserRequestFast(prompt: string, page: Page) { +export async function sendUserRequestFast(prompt: string, page: Page) { const locator: Locator = page.locator("#phraseDiv"); await locator.waitFor({ timeout: 120000, state: "visible" }); await locator.fill(prompt, { timeout: 30000 }); @@ -137,8 +154,13 @@ export async function sendUserRequestFast(prompt: string, page: Page) { * @param prompt The user request/prompt. * @param page The maing page from the electron host application. */ -export async function sendUserRequestAndWaitForResponse(prompt: string, page: Page): Promise { - const locators: Locator[] = await page.locator('.chat-message-agent-text').all(); +export async function sendUserRequestAndWaitForResponse( + prompt: string, + page: Page, +): Promise { + const locators: Locator[] = await page + .locator(".chat-message-agent-text") + .all(); // send the user request await sendUserRequest(prompt, page); @@ -155,21 +177,29 @@ export async function sendUserRequestAndWaitForResponse(prompt: string, page: Pa * @param page The maing page from the electron host application. */ export async function getLastAgentMessage(page: Page): Promise { - const locators: Locator[] = await page.locator('.chat-message-agent-text').all(); + const locators: Locator[] = await page + .locator(".chat-message-agent-text") + .all(); return locators[0].innerText(); } /** - * + * * @param page The page where the chatview is hosted * @param timeout The maximum amount of time to wait for the agent message * @param expectedMessageCount The expected # of agent messages at this time. * @returns When the expected # of messages is reached or the timeout is reached. Whichever occurrs first. */ -export async function waitForAgentMessage(page: Page, timeout: number, expectedMessageCount?: number | undefined): Promise { +export async function waitForAgentMessage( + page: Page, + timeout: number, + expectedMessageCount?: number | undefined, +): Promise { let timeWaited = 0; - let locators: Locator[] = await page.locator('.chat-message-agent-text').all(); + let locators: Locator[] = await page + .locator(".chat-message-agent-text") + .all(); let originalAgentMessageCount = locators.length; let messageCount = originalAgentMessageCount; @@ -180,20 +210,22 @@ export async function waitForAgentMessage(page: Page, timeout: number, expectedM do { await page.waitForTimeout(1000); timeWaited += 1000; - - locators = await page.locator('.chat-message-agent-text').all(); - messageCount = locators.length; - } while (timeWaited <= timeout && messageCount == originalAgentMessageCount); + locators = await page.locator(".chat-message-agent-text").all(); + messageCount = locators.length; + } while ( + timeWaited <= timeout && + messageCount == originalAgentMessageCount + ); } export function deleteTestProfiles() { const profileDir = path.join(os.homedir(), ".typeagent", "profiles"); if (fs.existsSync(profileDir)) { - fs.readdirSync(profileDir).map((dirEnt) =>{ + fs.readdirSync(profileDir).map((dirEnt) => { if (dirEnt.startsWith("test_")) { - const dir: string = path.join(profileDir, dirEnt) + const dir: string = path.join(profileDir, dirEnt); try { fs.rmSync(dir, { recursive: true, force: true }); } catch (e) { diff --git a/ts/tools/scripts/getKeys.mjs b/ts/tools/scripts/getKeys.mjs index 00f979608..e2f836cfa 100644 --- a/ts/tools/scripts/getKeys.mjs +++ b/ts/tools/scripts/getKeys.mjs @@ -39,7 +39,7 @@ async function getSecretListWithElevation(keyVaultClient, vaultName) { roleName: "Key Vault Administrator", expirationType: "AfterDuration", expirationDuration: "PT5M", // activate for 5 minutes - continueOnFailure: true + continueOnFailure: true, }); // Wait for the role to be activated @@ -47,8 +47,12 @@ async function getSecretListWithElevation(keyVaultClient, vaultName) { console.warn(chalk.yellowBright("Waiting 5 seconds...")); await new Promise((res) => setTimeout(res, 5000)); } catch (e) { - console.warn(chalk.yellow("Elevation failed...attempting to get secrets without elevation.")); - } + console.warn( + chalk.yellow( + "Elevation failed...attempting to get secrets without elevation.", + ), + ); + } return await keyVaultClient.getSecrets(vaultName); } } From c35ef2cb87fb64d78e052b747dcc151dc934f8ba Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 21:33:41 -0800 Subject: [PATCH 094/138] added token perns --- .github/workflows/build-ts.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index 8e69799be..a4352eefd 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -21,6 +21,7 @@ concurrency: permissions: pull-requests: read contents: read + id-token: write env: NODE_OPTIONS: --max_old_space_size=8192 From 6127fada52fd881f27c369c133b9463f215446f5 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 22:45:43 -0800 Subject: [PATCH 095/138] changed if condition for windows --- .github/workflows/build-ts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index a4352eefd..fec51be81 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -104,7 +104,7 @@ jobs: working-directory: ts/packages/cli continue-on-error: true - name: Shell Tests - smoke - if: ${{ matrix.os }} == "windows-latest" + if: ${{ runner.os == 'windows' }} timeout-minutes: 60 run: | npx playwright test simple.spec.ts From b28981b1bd5806feb06649b88c4df51d25ab9b2f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 22:46:20 -0800 Subject: [PATCH 096/138] shell full tests on main brainch only --- .github/workflows/shell-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index a0c5b33f7..e82957b8f 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -4,7 +4,7 @@ name: Shell Tests - Full Suite on: push: - branches: [ "main", "dev/robgruen/electron_tests" ] + branches: [ "main" ] schedule: - cron: "0 0 * * *" #start of every day From f7c22dfeaa4a1da5f916b36624307889ec94994f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 22:48:59 -0800 Subject: [PATCH 097/138] fixed merge issue --- .github/workflows/shell-tests.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index e82957b8f..442379fd3 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -25,10 +25,6 @@ jobs: #os: ["ubuntu-latest", "windows-latest", "macos-latest"] os: ["windows-latest"] version: [18, 20, 22] - exclude: - # TODO: Test timeout jest work for some reason - - os: windows-latest - version: 22 runs-on: ${{ matrix.os }} continue-on-error: true From 2db0bfdfa9d7ab9f8be9d25b7887c3b1cc0b281a Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 23:14:40 -0800 Subject: [PATCH 098/138] only install playwright on windows boxes --- .github/workflows/build-ts.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index fec51be81..e78fe4a08 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -71,6 +71,7 @@ jobs: run: | pnpm install --frozen-lockfile --strict-peer-dependencies - name: Install Playwright Browsers + if: ${{ runner.os == 'windows' }} run: pnpm exec playwright install --with-deps working-directory: ts/packages/shell - name: Build From 6c70da9a5526afa20fc5f00e99c68c84ecd93749 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 27 Jan 2025 23:45:28 -0800 Subject: [PATCH 099/138] updated to not run on merge_group --- .github/workflows/build-ts.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index e78fe4a08..39b421462 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -90,22 +90,25 @@ jobs: run: | npm run lint - name: Login to Azure + if: ${{ github.event_name != 'merge_group' }} uses: azure/login@v2.2.0 with: client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} - - name: Get Keys + - name: Get Keys + if: ${{ github.event_name != 'merge_group' }} run: | node tools/scripts/getKeys.mjs --vault build-pipeline-kv working-directory: ts - name: Test CLI - smoke + if: ${{ github.event_name != 'merge_group' }} run: | npm run start:dev 'prompt' 'why is the sky blue' working-directory: ts/packages/cli continue-on-error: true - name: Shell Tests - smoke - if: ${{ runner.os == 'windows' }} + if: ${{ github.event_name != 'merge_group' && runner.os =='windows' }} timeout-minutes: 60 run: | npx playwright test simple.spec.ts From a839a1c75b9902afe880f32b306d01db79370d27 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Tue, 28 Jan 2025 13:53:24 -0800 Subject: [PATCH 100/138] shell tests only run on schedule, not push --- .github/workflows/shell-tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 442379fd3..ed8858db2 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -3,10 +3,10 @@ name: Shell Tests - Full Suite on: - push: - branches: [ "main" ] - schedule: - - cron: "0 0 * * *" #start of every day + # push: + # branches: [ "main" ] + schedule: + - cron: "0 0 * * *" #start of every day permissions: id-token: write From 67eabf5a8139082e5a81e8f363ce8f2810ea30da Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Tue, 28 Jan 2025 13:59:34 -0800 Subject: [PATCH 101/138] fixed merge issue --- .github/workflows/build-ts.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index 9ec289866..8bfdb65c2 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -22,7 +22,6 @@ permissions: pull-requests: read contents: read id-token: write - id-token: write env: NODE_OPTIONS: --max_old_space_size=8192 From 8d59ec4c8d94a9d269c55f22c1a1a53c98b18320 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Tue, 28 Jan 2025 13:59:56 -0800 Subject: [PATCH 102/138] fixed other merge issue --- .github/workflows/build-ts.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index 8bfdb65c2..a9c43b97a 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -33,10 +33,6 @@ jobs: matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] version: [18, 20, 22] - exclude: - # TODO: Test timeout jest work for some reason - - os: windows-latest - version: 22 runs-on: ${{ matrix.os }} steps: From 25ae3b110465b91be4ae93fe4361460cd3fa76fb Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Tue, 28 Jan 2025 14:20:00 -0800 Subject: [PATCH 103/138] changing getKeys call to use npm --- .github/workflows/build-ts.yml | 2 +- ts/package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index a9c43b97a..76ea717cd 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -95,7 +95,7 @@ jobs: - name: Get Keys if: ${{ github.event_name != 'merge_group' }} run: | - node tools/scripts/getKeys.mjs --vault build-pipeline-kv + npm run getKeys:build working-directory: ts - name: Test CLI - smoke if: ${{ github.event_name != 'merge_group' }} diff --git a/ts/package.json b/ts/package.json index c6a1d9cf8..3fa729299 100644 --- a/ts/package.json +++ b/ts/package.json @@ -23,6 +23,7 @@ "cli:dev": "pnpm -C packages/cli run start:dev", "elevate": "node tools/scripts/elevate.js", "getKeys": "node tools/scripts/getKeys.mjs", + "getKeys:build": "node tools/scripts/getKeys.mjs --vault build-pipeline-kv", "postinstall": "cd node_modules/.pnpm/node_modules/better-sqlite3 && pnpm exec prebuild-install && shx cp build/Release/better_sqlite3.node build/Release/better_sqlite3.n.node", "knowledgeVisualizer": "pnpm -C packages/knowledgeVisualizer exec npm run start", "kv": "pnpm -C packages/knowledgeVisualizer exec npm run start", From 5afd98329683a955aed4b6ca235e4b2f1d646e6e Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Tue, 28 Jan 2025 15:18:37 -0800 Subject: [PATCH 104/138] try to emulate virtual display on linux --- .github/workflows/build-ts.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index 76ea717cd..182f6e775 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -103,7 +103,7 @@ jobs: npm run start:dev 'prompt' 'why is the sky blue' working-directory: ts/packages/cli continue-on-error: true - - name: Shell Tests - smoke + - name: Shell Tests - smoke Windows if: ${{ github.event_name != 'merge_group' && runner.os == 'windows' && matrix.version == '22' }} timeout-minutes: 60 run: | @@ -111,6 +111,15 @@ jobs: rm ../../.env working-directory: ts/packages/shell continue-on-error: true + - name: Shell Tests - smoke Linux + if: ${{ github.event_name != 'merge_group' && runner.os == 'Linux' && matrix.version == '22' }} + timeout-minutes: 60 + run: | + Xvfb :99 -screen 0 1600x1200x24 & export DISPLAY=:99 + npx playwright test simple.spec.ts + rm ../../.env + working-directory: ts/packages/shell + continue-on-error: true - name: Live Tests if: ${{ github.event_name != 'merge_group' && runner.os == 'linux' && matrix.version == '22' }} timeout-minutes: 60 From 440a308c6bb085dfb1d20991f76636669a0f662b Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Tue, 28 Jan 2025 15:25:24 -0800 Subject: [PATCH 105/138] emulate display on linux --- .github/workflows/build-ts.yml | 74 ++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index f64f1eaf7..27798e64b 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -85,37 +85,43 @@ jobs: working-directory: ts run: | npm run lint - # - name: Login to Azure - # if: ${{ github.event_name != 'merge_group' }} - # uses: azure/login@v2.2.0 - # with: - # client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} - # tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} - # subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} - # - name: Get Keys - # if: ${{ github.event_name != 'merge_group' }} - # run: | - # node tools/scripts/getKeys.mjs --vault build-pipeline-kv - # working-directory: ts - # - name: Test CLI - smoke - # if: ${{ github.event_name != 'merge_group' }} - # run: | - # npm run start:dev 'prompt' 'why is the sky blue' - # working-directory: ts/packages/cli - # continue-on-error: true - # - name: Shell Tests - smoke - # if: ${{ github.event_name != 'merge_group' && runner.os == 'windows' && matrix.version == '22' }} - # timeout-minutes: 60 - # run: | - # npx playwright test simple.spec.ts - # rm ../../.env - # working-directory: ts/packages/shell - # continue-on-error: true - # - name: Live Tests - # if: ${{ github.event_name != 'merge_group' && runner.os == 'linux' && matrix.version == '22' }} - # timeout-minutes: 60 - # run: | - # npm run test:live - # working-directory: ts - # continue-on-error: true - + - name: Login to Azure + if: ${{ github.event_name != 'merge_group' }} + uses: azure/login@v2.2.0 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} + - name: Get Keys + if: ${{ github.event_name != 'merge_group' }} + run: | + node tools/scripts/getKeys.mjs --vault build-pipeline-kv + working-directory: ts + - name: Test CLI - smoke + if: ${{ github.event_name != 'merge_group' }} + run: | + npm run start:dev 'prompt' 'why is the sky blue' + working-directory: ts/packages/cli + continue-on-error: true + - name: Shell Tests - smoke (windows) + if: ${{ github.event_name != 'merge_group' && runner.os == 'windows' && matrix.version == '22' }} + timeout-minutes: 60 + run: | + npx playwright test simple.spec.ts + rm ../../.env + - name: Shell Tests - smoke (linux) + if: ${{ github.event_name != 'merge_group' && runner.os == 'Linux' && matrix.version == '22' }} + timeout-minutes: 60 + run: | + Xvfb :99 -screen 0 1600x1200x24 & export DISPLAY=:99 + npx playwright test simple.spec.ts + rm ../../.env + working-directory: ts/packages/shell + continue-on-error: true + - name: Live Tests + if: ${{ github.event_name != 'merge_group' && runner.os == 'linux' && matrix.version == '22' }} + timeout-minutes: 60 + run: | + npm run test:live + working-directory: ts + continue-on-error: true \ No newline at end of file From b54d270b943479d31ba9bda6e7170b5e2e7dfeee Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 10:01:04 -0800 Subject: [PATCH 106/138] only test one version of node --- .github/workflows/shell-tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index b7939e12d..00ba81c36 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -24,7 +24,8 @@ jobs: matrix: #os: ["ubuntu-latest", "windows-latest", "macos-latest"] os: ["windows-latest"] - version: [18, 20, 22] + #version: [18, 20, 22] + version: [20] runs-on: ${{ matrix.os }} continue-on-error: true From bba4e151a56b9137fa7b7cac5b985c4b608a2662 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 10:17:30 -0800 Subject: [PATCH 107/138] added pull_requests_target workflow for forked PR requests --- .../build-ts-pull-request-target.yml | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 .github/workflows/build-ts-pull-request-target.yml diff --git a/.github/workflows/build-ts-pull-request-target.yml b/.github/workflows/build-ts-pull-request-target.yml new file mode 100644 index 000000000..dd2680d62 --- /dev/null +++ b/.github/workflows/build-ts-pull-request-target.yml @@ -0,0 +1,125 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# This workflow will build the TypeAgent TypeScript code + +name: build-ts + +on: + # Manual approval of forked environments is required + pull_request_target: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + pull-requests: read + contents: read + id-token: write + +env: + NODE_OPTIONS: --max_old_space_size=8192 + +jobs: + build_ts: + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + version: [18, 20, 22] + + runs-on: ${{ matrix.os }} + environment: development-forks + steps: + - if: runner.os == 'Linux' + run: | + sudo apt install libsecret-1-0 + - name: Setup Git LF + run: | + git config --global core.autocrlf false + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + ts: + - "ts/**" + - ".github/workflows/build-ts.yml" + - uses: pnpm/action-setup@v4 + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + name: Install pnpm + with: + package_json_file: ts/package.json + - uses: actions/setup-node@v4 + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + with: + node-version: ${{ matrix.version }} + cache: "pnpm" + cache-dependency-path: ts/pnpm-lock.yaml + - name: Install dependencies + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + working-directory: ts + run: | + pnpm install --frozen-lockfile --strict-peer-dependencies + - name: Install Playwright Browsers + if: ${{ runner.os == 'windows' && matrix.version == '22' }} + run: pnpm exec playwright install --with-deps + working-directory: ts/packages/shell + - name: Build + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + working-directory: ts + run: | + npm run build + - name: Test + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + working-directory: ts + run: | + npm run test:local + - name: Lint + if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} + working-directory: ts + run: | + npm run lint + - name: Login to Azure + if: ${{ github.event_name != 'merge_group' }} + uses: azure/login@v2.2.0 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} + - name: Get Keys + if: ${{ github.event_name != 'merge_group' }} + run: | + node tools/scripts/getKeys.mjs --vault build-pipeline-kv + working-directory: ts + - name: Test CLI - smoke + if: ${{ github.event_name != 'merge_group' }} + run: | + npm run start:dev 'prompt' 'why is the sky blue' + working-directory: ts/packages/cli + continue-on-error: true + - name: Shell Tests - smoke (windows) + if: ${{ github.event_name != 'merge_group' && runner.os == 'windows' && matrix.version == '22' }} + timeout-minutes: 60 + run: | + npx playwright test simple.spec.ts + rm ../../.env + - name: Shell Tests - smoke (linux) + if: ${{ github.event_name != 'merge_group' && runner.os == 'Linux' && matrix.version == '22' }} + timeout-minutes: 60 + run: | + Xvfb :99 -screen 0 1600x1200x24 & export DISPLAY=:99 + npx playwright test simple.spec.ts + rm ../../.env + working-directory: ts/packages/shell + continue-on-error: true + - name: Live Tests + if: ${{ github.event_name != 'merge_group' && runner.os == 'linux' && matrix.version == '22' }} + timeout-minutes: 60 + run: | + npm run test:live + working-directory: ts + continue-on-error: true \ No newline at end of file From 40a3375314c2222370e968b834a002f7e48c0963 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 20:31:41 -0800 Subject: [PATCH 108/138] fixed merge coflict --- .github/workflows/shell-tests.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 138c8231b..3e4481467 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -26,13 +26,7 @@ jobs: strategy: fail-fast: false matrix: - #os: ["ubuntu-latest", "windows-latest", "macos-latest"] -<<<<<<< HEAD os: ["windows-latest"] - #version: [18, 20, 22] -======= - os: ["windows-latest", "ubuntu-latest"] ->>>>>>> main version: [20] runs-on: ${{ matrix.os }} From 51a28decb39cc7ed8ea6af576ea1ea8f644b1836 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 20:32:06 -0800 Subject: [PATCH 109/138] updated trigger --- .github/workflows/shell-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 3e4481467..ea8a2af1d 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -3,8 +3,8 @@ name: Shell Tests - Full Suite on: - #push: - # branches: ["main"] + push: + branches: ["main", "dev/robgruen/electron_tests"] schedule: - cron: "0 0 * * *" #start of every day From a3cdcd05047b6f69d68ea9a59fa014876a1201ed Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 20:49:10 -0800 Subject: [PATCH 110/138] formatting --- .github/workflows/shell-tests.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index ea8a2af1d..8cae83cec 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -41,7 +41,9 @@ jobs: - name: Setup Git LF run: | git config --global core.autocrlf false + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 id: filter with: @@ -49,36 +51,44 @@ jobs: ts: - "ts/**" - ".github/workflows/build-ts.yml" - - uses: pnpm/action-setup@v4 + + - uses: pnpm/action-setup@v4 name: Install pnpm with: package_json_file: ts/package.json + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.version }} cache: "pnpm" cache-dependency-path: ts/pnpm-lock.yaml + - name: Install dependencies (pnpm) working-directory: ts run: | pnpm install --frozen-lockfile --strict-peer-dependencies + - name: Install Playwright Browsers run: pnpm exec playwright install --with-deps working-directory: ts/packages/shell + - name: Build repo working-directory: ts run: | npm run build + - name: Login to Azure uses: azure/login@v2.2.0 with: client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} + - name: Get Keys run: | node tools/scripts/getKeys.mjs --vault build-pipeline-kv working-directory: ts + - name: Test CLI - verify .env & endpoint connectivity run: | npm run start:dev 'prompt' 'why is the sky blue' @@ -104,8 +114,7 @@ jobs: rm ../../.env working-directory: ts/packages/shell continue-on-error: true - - + - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: From 4fa161ba0ef4ef71ad9b046c9ae88e2accb5c29f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 20:52:39 -0800 Subject: [PATCH 111/138] trying npx again --- .github/workflows/shell-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 8cae83cec..1a1d7a4ba 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -98,7 +98,7 @@ jobs: if: ${{ runner.os == 'windows' }} timeout-minutes: 60 run: | - npm run shell:test + npx playwright test rm ../../.env working-directory: ts/packages/shell continue-on-error: true From c28c9e986ae0cefdbba9aefe45546d6be306848d Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 20:53:16 -0800 Subject: [PATCH 112/138] fixed error --- .github/workflows/shell-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 1a1d7a4ba..e9abb7a44 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -52,7 +52,7 @@ jobs: - "ts/**" - ".github/workflows/build-ts.yml" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v4 name: Install pnpm with: package_json_file: ts/package.json From a1700d332a37008466d1b56734177722a03462d6 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 21:12:23 -0800 Subject: [PATCH 113/138] small changes --- .github/workflows/shell-tests.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index e9abb7a44..e4698efdc 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -30,13 +30,10 @@ jobs: version: [20] runs-on: ${{ matrix.os }} - continue-on-error: true steps: - if: runner.os == 'Linux' - # https://github.com/microsoft/playwright/issues/34251 - sysctl command run: | sudo apt install libsecret-1-0 - sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Setup Git LF run: | @@ -98,7 +95,7 @@ jobs: if: ${{ runner.os == 'windows' }} timeout-minutes: 60 run: | - npx playwright test + npm run shell:test rm ../../.env working-directory: ts/packages/shell continue-on-error: true From c86da5581bb5208787f5ec3de30fc0aae6598a52 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 21:15:38 -0800 Subject: [PATCH 114/138] removed --no-sandbox --- ts/packages/shell/test/simple.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts index b6b235bb6..4ae374909 100644 --- a/ts/packages/shell/test/simple.spec.ts +++ b/ts/packages/shell/test/simple.spec.ts @@ -23,7 +23,7 @@ test("simple", { tag: "@smoke" }, async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); const app: ElectronApplication = await electron.launch({ - args: [getAppPath(), "--no-sandbox"], + args: [getAppPath()], }); const mainWindow: Page = await app.firstWindow(); await mainWindow.bringToFront(); From 8d8ed6eda9daae334649ee47d84d4354d42e892f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 21:16:02 -0800 Subject: [PATCH 115/138] removed debugger flag --- .github/workflows/shell-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index e4698efdc..d6d9c6c7c 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -14,7 +14,7 @@ permissions: env: NODE_OPTIONS: --max_old_space_size=8192 - DEBUG: pw:browser* +# DEBUG: pw:browser* # run only one of these at a time concurrency: From 48b47ea6b17ec26f5e560a24ef6e78ae866d28cf Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 21:17:09 -0800 Subject: [PATCH 116/138] added electron debugging flag --- .github/workflows/shell-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index d6d9c6c7c..686ff744c 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -15,6 +15,7 @@ permissions: env: NODE_OPTIONS: --max_old_space_size=8192 # DEBUG: pw:browser* + ELECTRON_ENABLE_LOGGING: true # run only one of these at a time concurrency: From 6963b8d973d85debab560758a80bf9e01b111410 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 21:35:48 -0800 Subject: [PATCH 117/138] added typeagent tracing --- .github/workflows/shell-tests.yml | 1 + ts/packages/shell/test/testHelper.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 686ff744c..2f2019168 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -16,6 +16,7 @@ env: NODE_OPTIONS: --max_old_space_size=8192 # DEBUG: pw:browser* ELECTRON_ENABLE_LOGGING: true + DEBUG: typeagent:* # run only one of these at a time concurrency: diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index e66e84c12..ca9aaa336 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -68,6 +68,7 @@ export async function startShell(): Promise { retryAttempt++; if (runningApplications.get(process.env["INSTANCE_NAME"])) { + console.log(`Closing instance ${process.env["INSTANCE_NAME"]}`); await runningApplications .get(process.env["INSTANCE_NAME"]!)! .close(); @@ -87,12 +88,15 @@ async function getMainWindow(app: ElectronApplication): Promise { await window.waitForLoadState("domcontentloaded"); // is this the correct window? - if ((await window.title()).length > 0) { + const title = await window.title(); + if (title.length > 0) { + console.log(`Found window ${title}`); return window; } // if we change the # of windows beyond 2 we'll have to update this function to correctly disambiguate which window is the correct one if (app.windows.length > 2) { + console.log(`Found ${app.windows.length} windows. Expected 2`); throw "Please update this logic to select the correct main window. (testHelper.ts->getMainWindow())"; } From 46faa9fb779041fc89c1894f214f9a82e7917eda Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 21:45:24 -0800 Subject: [PATCH 118/138] more logging --- ts/packages/shell/test/testHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index ca9aaa336..36d32c9f0 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -63,7 +63,7 @@ export async function startShell(): Promise { return mainWindow; } catch (e) { console.warn( - `Unable to start electrom application (${process.env["INSTANCE_NAME"]}). Attempt ${retryAttempt} of ${maxRetries}`, + `Unable to start electrom application (${process.env["INSTANCE_NAME"]}). Attempt ${retryAttempt} of ${maxRetries}. Error: ${e}`, ); retryAttempt++; From 22c16e38cd08812dda8d1801342bbfaf9586a70d Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 21:57:28 -0800 Subject: [PATCH 119/138] added timeout for first window --- .github/workflows/shell-tests.yml | 2 +- ts/packages/shell/test/testHelper.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 2f2019168..351848e5a 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -15,7 +15,7 @@ permissions: env: NODE_OPTIONS: --max_old_space_size=8192 # DEBUG: pw:browser* - ELECTRON_ENABLE_LOGGING: true +# ELECTRON_ENABLE_LOGGING: true DEBUG: typeagent:* # run only one of these at a time diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 36d32c9f0..0ef91fefc 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -84,7 +84,7 @@ export async function startShell(): Promise { } async function getMainWindow(app: ElectronApplication): Promise { - const window: Page = await app.firstWindow(); + const window: Page = await app.firstWindow({ timeout: 30000}); await window.waitForLoadState("domcontentloaded"); // is this the correct window? @@ -101,6 +101,8 @@ async function getMainWindow(app: ElectronApplication): Promise { } // since there are only two windows we know that if the first one isn't the right one we can just return the second one + await app.windows[app.windows.length - 1].waitForLoadState("domcontentloaded"); + return app.windows[app.windows.length - 1]; } From 886392e1981386453323c910b71de94618a7870e Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 22:12:31 -0800 Subject: [PATCH 120/138] better window finding logic --- ts/packages/shell/test/testHelper.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 0ef91fefc..d257d2343 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -101,9 +101,24 @@ async function getMainWindow(app: ElectronApplication): Promise { } // since there are only two windows we know that if the first one isn't the right one we can just return the second one - await app.windows[app.windows.length - 1].waitForLoadState("domcontentloaded"); - - return app.windows[app.windows.length - 1]; + let ww = app.windows[app.windows.length - 1]; + let index = 0; + do { + + try { + await app.windows[app.windows.length - 1].waitForLoadState("domcontentloaded"); + + return app.windows[app.windows.length - 1]; + } catch (e) { + console.log(e); + + await new Promise((resolve) => setTimeout(resolve, 1000)) + } + + console.log("waiting..."); + } while (app.windows[app.windows.length - 1] === undefined && ++index < 30); + + throw "Unable to find window...timeout"; } /** From df8585c23e951b527b32cfc956627ab1ae55826b Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Wed, 29 Jan 2025 22:17:17 -0800 Subject: [PATCH 121/138] logic update --- ts/packages/shell/test/testHelper.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index d257d2343..6100b4b52 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -106,16 +106,18 @@ async function getMainWindow(app: ElectronApplication): Promise { do { try { - await app.windows[app.windows.length - 1].waitForLoadState("domcontentloaded"); + if (app.windows[app.windows.length - 1] !== undefined) { + await app.windows[app.windows.length - 1].waitForLoadState("domcontentloaded"); + + return app.windows[app.windows.length - 1]; + } + + console.log("waiting..."); + await new Promise((resolve) => setTimeout(resolve, 1000)) - return app.windows[app.windows.length - 1]; } catch (e) { console.log(e); - - await new Promise((resolve) => setTimeout(resolve, 1000)) } - - console.log("waiting..."); } while (app.windows[app.windows.length - 1] === undefined && ++index < 30); throw "Unable to find window...timeout"; From 2dcd5403ad202784d22a15138f888d639b6450d2 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 30 Jan 2025 06:46:19 -0800 Subject: [PATCH 122/138] updated window finding logic --- ts/packages/shell/test/testHelper.ts | 74 +++++++++++++++++----------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 6100b4b52..4753d4917 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -84,41 +84,55 @@ export async function startShell(): Promise { } async function getMainWindow(app: ElectronApplication): Promise { - const window: Page = await app.firstWindow({ timeout: 30000}); - await window.waitForLoadState("domcontentloaded"); - - // is this the correct window? - const title = await window.title(); - if (title.length > 0) { - console.log(`Found window ${title}`); - return window; - } - - // if we change the # of windows beyond 2 we'll have to update this function to correctly disambiguate which window is the correct one - if (app.windows.length > 2) { - console.log(`Found ${app.windows.length} windows. Expected 2`); - throw "Please update this logic to select the correct main window. (testHelper.ts->getMainWindow())"; - } - - // since there are only two windows we know that if the first one isn't the right one we can just return the second one - let ww = app.windows[app.windows.length - 1]; - let index = 0; + // const window: Page = await app.firstWindow({ timeout: 30000}); + // await window.waitForLoadState("domcontentloaded"); + + // // is this the correct window? + // const title = await window.title(); + // if (title.length > 0) { + // console.log(`Found window ${title}`); + // return window; + // } + + // // if we change the # of windows beyond 2 we'll have to update this function to correctly disambiguate which window is the correct one + // if (app.windows.length > 2) { + // console.log(`Found ${app.windows.length} windows. Expected 2`); + // throw "Please update this logic to select the correct main window. (testHelper.ts->getMainWindow())"; + // } + + let attempts = 0; do { - try { - if (app.windows[app.windows.length - 1] !== undefined) { - await app.windows[app.windows.length - 1].waitForLoadState("domcontentloaded"); - - return app.windows[app.windows.length - 1]; - } + let windows: Page[] = await app.windows(); - console.log("waiting..."); - await new Promise((resolve) => setTimeout(resolve, 1000)) + // if we change the # of windows beyond 2 we'll have to update this function to correctly disambiguate which window is the correct one + if (windows.length > 2) { + console.log(`Found ${app.windows.length} windows. Expected 2`); + throw "Please update this logic to select the correct main window. (testHelper.ts->getMainWindow())"; + } - } catch (e) { - console.log(e); + // wait for each window to load and return the one we are interested in + for(let i = 0; i < windows.length; i++) { + try { + if (windows[i] !== undefined) { + await windows[i].waitForLoadState("domcontentloaded"); + + // is this the correct window? + const title = await windows[i].title(); + if (title.length > 0) { + console.log(`Found window ${title}`); + return windows[i]; + } + } + } catch (e) { + console.log(e); + } } - } while (app.windows[app.windows.length - 1] === undefined && ++index < 30); + + console.log("waiting..."); + await new Promise((resolve) => setTimeout(resolve, 1000)) + + } while (++attempts < 30); throw "Unable to find window...timeout"; } From 11975659e7a873c28d7bf86129a0fe1aaf7183f7 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 30 Jan 2025 06:58:14 -0800 Subject: [PATCH 123/138] small refactoring, turned off debug messages re-enabled linux --- .github/workflows/shell-tests.yml | 8 ++++---- ts/packages/shell/test/testHelper.ts | 16 ---------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 351848e5a..36713e264 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -14,9 +14,9 @@ permissions: env: NODE_OPTIONS: --max_old_space_size=8192 -# DEBUG: pw:browser* -# ELECTRON_ENABLE_LOGGING: true - DEBUG: typeagent:* +# DEBUG: pw:browser* # PlayWright debug messages +# ELECTRON_ENABLE_LOGGING: true # Electron debug messages +# DEBUG: typeagent:* # TypeAgent debug messages # run only one of these at a time concurrency: @@ -28,7 +28,7 @@ jobs: strategy: fail-fast: false matrix: - os: ["windows-latest"] + os: ["windows-latest", "ubuntu-latest"] version: [20] runs-on: ${{ matrix.os }} diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 4753d4917..a93eecd2a 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -84,22 +84,6 @@ export async function startShell(): Promise { } async function getMainWindow(app: ElectronApplication): Promise { - // const window: Page = await app.firstWindow({ timeout: 30000}); - // await window.waitForLoadState("domcontentloaded"); - - // // is this the correct window? - // const title = await window.title(); - // if (title.length > 0) { - // console.log(`Found window ${title}`); - // return window; - // } - - // // if we change the # of windows beyond 2 we'll have to update this function to correctly disambiguate which window is the correct one - // if (app.windows.length > 2) { - // console.log(`Found ${app.windows.length} windows. Expected 2`); - // throw "Please update this logic to select the correct main window. (testHelper.ts->getMainWindow())"; - // } - let attempts = 0; do { From c89a0b2212693541d922d94b6d2e1c864bca3c99 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 30 Jan 2025 07:04:47 -0800 Subject: [PATCH 124/138] remove linux, &branch --- .github/workflows/shell-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 36713e264..ec18cd1f4 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -4,7 +4,7 @@ name: Shell Tests - Full Suite on: push: - branches: ["main", "dev/robgruen/electron_tests"] + branches: ["main"] schedule: - cron: "0 0 * * *" #start of every day @@ -28,7 +28,7 @@ jobs: strategy: fail-fast: false matrix: - os: ["windows-latest", "ubuntu-latest"] + os: ["windows-latest"] version: [20] runs-on: ${{ matrix.os }} From c9392c9a97ad7175dce8041b8551ea950cb8f614 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 30 Jan 2025 07:16:51 -0800 Subject: [PATCH 125/138] ran pretier --- ts/packages/shell/test/testHelper.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index a93eecd2a..727340d45 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -84,9 +84,8 @@ export async function startShell(): Promise { } async function getMainWindow(app: ElectronApplication): Promise { - let attempts = 0; + let attempts = 0; do { - let windows: Page[] = await app.windows(); // if we change the # of windows beyond 2 we'll have to update this function to correctly disambiguate which window is the correct one @@ -96,26 +95,25 @@ async function getMainWindow(app: ElectronApplication): Promise { } // wait for each window to load and return the one we are interested in - for(let i = 0; i < windows.length; i++) { + for (let i = 0; i < windows.length; i++) { try { if (windows[i] !== undefined) { await windows[i].waitForLoadState("domcontentloaded"); - + // is this the correct window? const title = await windows[i].title(); if (title.length > 0) { console.log(`Found window ${title}`); return windows[i]; } - } + } } catch (e) { console.log(e); - } + } } console.log("waiting..."); - await new Promise((resolve) => setTimeout(resolve, 1000)) - + await new Promise((resolve) => setTimeout(resolve, 1000)); } while (++attempts < 30); throw "Unable to find window...timeout"; From 17a98069a17f0f83bb599b3703a7be028e2e6760 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 30 Jan 2025 07:26:44 -0800 Subject: [PATCH 126/138] removed branch push trigger --- .github/workflows/shell-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index ec18cd1f4..c881ffe1c 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -3,8 +3,8 @@ name: Shell Tests - Full Suite on: - push: - branches: ["main"] + #push: + # branches: ["main"] schedule: - cron: "0 0 * * *" #start of every day From 0644b7332ec22560d9f023241b53a3cc1989cee2 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 2 Feb 2025 12:50:44 -0800 Subject: [PATCH 127/138] added the ability to handle streaming agent messages. --- ts/packages/shell/test/simple.spec.ts | 10 ++- ts/packages/shell/test/testHelper.ts | 107 +++++++++++++++++++++++--- 2 files changed, 103 insertions(+), 14 deletions(-) diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts index 4ae374909..7cae606dc 100644 --- a/ts/packages/shell/test/simple.spec.ts +++ b/ts/packages/shell/test/simple.spec.ts @@ -11,6 +11,7 @@ import test, { import { exitApplication, getAppPath, + sendUserRequestAndWaitForCompletion, sendUserRequestAndWaitForResponse, startShell, } from "./testHelper"; @@ -30,22 +31,23 @@ test("simple", { tag: "@smoke" }, async ({}, testInfo) => { await app.close(); }); -test.skip("why is the sky blue?", { tag: "@smoke" }, async ({}, testInfo) => { +test("why is the sky blue?", { tag: "@smoke" }, async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); // launch the app const mainWindow: Page = await startShell(); - const msg = await sendUserRequestAndWaitForResponse( + const msg = await sendUserRequestAndWaitForCompletion( `why is the sky blue?`, mainWindow, + 1 ); expect( msg.toLowerCase(), "Chat agent didn't respond with the expected message.", - ).toContain("raleigh scattering."); + ).toContain("scattering."); // close the application await exitApplication(mainWindow); -}); +}); \ No newline at end of file diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 727340d45..4589645cd 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -22,7 +22,7 @@ const runningApplications: Map = new Map< /** * Starts the electron app and returns the main page after the greeting agent message has been posted. */ -export async function startShell(): Promise { +export async function startShell(waitForAgentGreeting: boolean = true): Promise { // this is needed to isolate these tests session from other concurrently running tests process.env["INSTANCE_NAME"] = `test_${process.env["TEST_WORKER_INDEX"]}_${process.env["TEST_PARALLEL_INDEX"]}`; @@ -58,7 +58,9 @@ export async function startShell(): Promise { const mainWindow: Page = await getMainWindow(app); // wait for agent greeting - await waitForAgentMessage(mainWindow, 30000, 1); + if (waitForAgentGreeting) { + await waitForAgentMessage(mainWindow, 30000, 1, true, ["..."]); + } return mainWindow; } catch (e) { @@ -169,7 +171,12 @@ export async function sendUserRequestFast(prompt: string, page: Page) { } /** - * Submits a user request to the system via the chat input box and then waits for the agent's response + * Submits a user request to the system via the chat input box and then waits for the first available response + * NOTE: If your expected response changes or you invoke multi-action flow you should be calling + * sendUserRequestAndAwaitSpecificResponse() instead of this call + * + * Remarks: Use this method when calling @commands...agent calls should use aforementioned function. + * * @param prompt The user request/prompt. * @param page The maing page from the electron host application. */ @@ -188,14 +195,39 @@ export async function sendUserRequestAndWaitForResponse( await waitForAgentMessage(page, 30000, locators.length + 1); // return the response - return await getLastAgentMessage(page); + return await getLastAgentMessageText(page); +} + +/** + * Submits a user request and awaits for completion of the response. + * + * Remarks: Call this function when expecting an agent action response. + * + * @param prompt The user request/prompt. + * @param page The page hosting the user shell + * @param expectedNumberOfAgentMessages The number of expected agent messages to wait for/receive + */ +export async function sendUserRequestAndWaitForCompletion(prompt: string, page: Page, expectedNumberOfAgentMessages: number = 1): Promise { + // TODO: implement + const locators: Locator[] = await page + .locator(".chat-message-agent-text") + .all(); + + // send the user request + await sendUserRequest(prompt, page); + + // wait for agent response + await waitForAgentMessage(page, 30000, locators.length + 1, true); + + // return the response + return await getLastAgentMessageText(page); } /** * Gets the last agent message from the chat view * @param page The maing page from the electron host application. */ -export async function getLastAgentMessage(page: Page): Promise { +export async function getLastAgentMessageText(page: Page): Promise { const locators: Locator[] = await page .locator(".chat-message-agent-text") .all(); @@ -203,26 +235,71 @@ export async function getLastAgentMessage(page: Page): Promise { return locators[0].innerText(); } +/** + * Gets the last agent message from the chat view + * @param page The maing page from the electron host application. + */ +export async function getLastAgentMessage(page: Page): Promise { + const locators: Locator[] = await page + .locator(".chat-message-container-agent") + .all(); + + return locators[0]; +} + +/** + * Determines if the supplied agent message/action has been completed + * + * @param msg The agent message to check for completion + */ +export async function isMessageCompleted(msg: Locator): Promise { + // Agent message is complete once the metrics have been reported + + let timeWaited = 0; + + do { + try { + const details: Locator = await msg.locator(".metrics-details", { hasText: "Total" }); + + if (await details.count() > 0) { + return true; + } + + } catch (e) { + // not found + } + } while (timeWaited < 30000); + + return false; +} + /** * * @param page The page where the chatview is hosted * @param timeout The maximum amount of time to wait for the agent message * @param expectedMessageCount The expected # of agent messages at this time. + * @param waitForMessageCompletion A flag indicating if we should block util the message is completed. + * @param ignore A list of messges that this method will consider noise and will reject as false positivies + * i.e. [".."] and this method will ignore agent messages that are "..." and will continue waiting. + * This is useful when an agent sends status messages. + * * @returns When the expected # of messages is reached or the timeout is reached. Whichever occurrs first. */ export async function waitForAgentMessage( page: Page, timeout: number, - expectedMessageCount?: number | undefined, + expectedMessageCount: number, + waitForMessageCompletion: boolean = false, + ignore: string[] = [] ): Promise { let timeWaited = 0; let locators: Locator[] = await page - .locator(".chat-message-agent-text") + .locator(".chat-message-container-agent") .all(); let originalAgentMessageCount = locators.length; let messageCount = originalAgentMessageCount; - - if (expectedMessageCount == messageCount) { + + if (expectedMessageCount == messageCount && (!waitForMessageCompletion || await isMessageCompleted(await getLastAgentMessage(page)))) { return; } @@ -230,8 +307,18 @@ export async function waitForAgentMessage( await page.waitForTimeout(1000); timeWaited += 1000; - locators = await page.locator(".chat-message-agent-text").all(); + locators = await page.locator(".chat-message-container-agent").all(); messageCount = locators.length; + + // is this message ignorable? + if (locators.length > 0) { + const lastMessage = await getLastAgentMessageText(page); + if (ignore.indexOf(lastMessage) > -1) { + console.log(`Ignore agent message '${lastMessage}'`); + messageCount = originalAgentMessageCount; + } + } + } while ( timeWaited <= timeout && messageCount == originalAgentMessageCount From 10b5fdf715f0020885be2d8d9dac3022e5b4c1ee Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Sun, 2 Feb 2025 12:59:07 -0800 Subject: [PATCH 128/138] fixed test --- ts/packages/shell/test/simple.spec.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts index a1b3d8b1d..1edca2c4b 100644 --- a/ts/packages/shell/test/simple.spec.ts +++ b/ts/packages/shell/test/simple.spec.ts @@ -13,7 +13,6 @@ import { getAppPath, sendUserRequestAndWaitForCompletion, getLaunchArgs, - sendUserRequestAndWaitForResponse, startShell, } from "./testHelper"; import { fileURLToPath } from "node:url"; @@ -32,10 +31,6 @@ test("startShell", { tag: "@smoke" }, async ({}) => { await startShell(); }); -test("startShell", { tag: "@smoke" }, async ({}) => { - await startShell(); -}); - test("why is the sky blue?", { tag: "@smoke" }, async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); @@ -51,7 +46,7 @@ test("why is the sky blue?", { tag: "@smoke" }, async ({}, testInfo) => { expect( msg.toLowerCase(), "Chat agent didn't respond with the expected message.", - ).toContain("scattering."); + ).toContain("scattering"); // close the application await exitApplication(mainWindow); From a83d7570657360b2709df21ad6e57bb40dbebffa Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 3 Feb 2025 09:38:16 -0800 Subject: [PATCH 129/138] started adding list agent tests --- ts/packages/shell/test/listAgent.spec.ts | 60 ++++++++++++++++++++++++ ts/packages/shell/test/testHelper.ts | 48 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 ts/packages/shell/test/listAgent.spec.ts diff --git a/ts/packages/shell/test/listAgent.spec.ts b/ts/packages/shell/test/listAgent.spec.ts new file mode 100644 index 000000000..23693cdac --- /dev/null +++ b/ts/packages/shell/test/listAgent.spec.ts @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import test, { + ElectronApplication, + Page, + _electron, + _electron as electron, + expect, +} from "@playwright/test"; +import { + exitApplication, + getAppPath, + sendUserRequestAndWaitForCompletion, + getLaunchArgs, + startShell, + testUserRequest +} from "./testHelper"; + +// Annotate entire file as serial. +test.describe.configure({ mode: "serial" }); + +test.describe("List Agent Tests", () => { + + test("create_update_list", async ({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); + + await testUserRequest( + [ + "create a shopping list", + "what's on the shopping list?", + "add eggs, milk, flower to the shopping list", + "what's on the shopping list?", + "remove milk from the grocery list", + "what's on the shopping list?" + ], + [ + "Created list: shopping", + "List 'shopping' is empty", + "Added items: eggs,milk,flour to list shopping", + "eggs\nmilk\nfoour", + "Removed items: milk from list grocery", + "eggs\nflour" + ]); + }); + + test("delete_list", async ({}, testInfo) => { + console.log(`Running test '${testInfo.title}`); + + await testUserRequest( + [ + "delete shopping list", + "is there a shopping list?" + ], + [ + "Cleared list: shopping", + "List 'shopping' is empty" + ]); + }); +}); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 58325682e..b66c87573 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -4,6 +4,7 @@ import { _electron as electron, ElectronApplication, + expect, Locator, Page, } from "@playwright/test"; @@ -334,6 +335,9 @@ export async function waitForAgentMessage( ); } +/** + * Deletes test profiles from agent storage + */ export function deleteTestProfiles() { const profileDir = path.join(os.homedir(), ".typeagent", "profiles"); @@ -350,3 +354,47 @@ export function deleteTestProfiles() { }); } } + +export type TestCallback = () => void; + +/** + * Encapsulates the supplied method within a startup and shutdown of teh + * shell. Test code executes between them. + */ +export async function runTestCalback(callback: TestCallback): Promise { + // launch the app + const mainWindow: Page = await startShell(); + + // run the supplied function + callback(); + + // close the application + await exitApplication(mainWindow); +} + +/** + * Encapsulates the supplied method within a startup and shutdown of teh + * shell. Test code executes between them. + */ +export async function testUserRequest(userRequests: string[], expectedResponses: string[]): Promise { + // launch the app + const mainWindow: Page = await startShell(); + + // issue the supplied requests and check their responses + for (let i = 0; i < userRequests.length; i++) { + const msg = await sendUserRequestAndWaitForCompletion( + userRequests[i], + mainWindow, + 1 + ); + + // verify expected result + expect( + msg.toLowerCase(), + `Chat agent didn't respond with the expected message. Request: '${userRequests[i]}', Response: '${expectedResponses[i]}'`, + ).toBe(expectedResponses[i]); + } + + // close the application + exitApplication(mainWindow); +} From e3f408e8d592e2c11597732312a4085320e4cd1b Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 3 Feb 2025 10:20:34 -0800 Subject: [PATCH 130/138] cleanup --- .github/workflows/build-ts.yml | 44 ---------------------------------- 1 file changed, 44 deletions(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index a10bcb36d..7a891ef71 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -66,10 +66,6 @@ jobs: working-directory: ts run: | pnpm install --frozen-lockfile --strict-peer-dependencies - # - name: Install Playwright Browsers - # if: ${{ runner.os == 'windows' && matrix.version == '22' }} - # run: pnpm exec playwright install --with-deps - # working-directory: ts/packages/shell - name: Build if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts == 'true' }} working-directory: ts @@ -85,43 +81,3 @@ jobs: working-directory: ts run: | npm run lint - # - name: Login to Azure - # if: ${{ github.event_name != 'merge_group' }} - # uses: azure/login@v2.2.0 - # with: - # client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} - # tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} - # subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} - # - name: Get Keys - # if: ${{ github.event_name != 'merge_group' }} - # run: | - # node tools/scripts/getKeys.mjs --vault build-pipeline-kv - # working-directory: ts - # - name: Test CLI - smoke - # if: ${{ github.event_name != 'merge_group' }} - # run: | - # npm run start:dev 'prompt' 'why is the sky blue' - # working-directory: ts/packages/cli - # continue-on-error: true - # - name: Shell Tests - smoke (windows) - # if: ${{ github.event_name != 'merge_group' && runner.os == 'windows' && matrix.version == '22' }} - # timeout-minutes: 60 - # run: | - # npx playwright test simple.spec.ts - # rm ../../.env - # - name: Shell Tests - smoke (linux) - # if: ${{ github.event_name != 'merge_group' && runner.os == 'Linux' && matrix.version == '22' }} - # timeout-minutes: 60 - # run: | - # Xvfb :99 -screen 0 1600x1200x24 & export DISPLAY=:99 - # npx playwright test simple.spec.ts - # rm ../../.env - # working-directory: ts/packages/shell - # continue-on-error: true - # - name: Live Tests - # if: ${{ github.event_name != 'merge_group' && runner.os == 'linux' && matrix.version == '22' }} - # timeout-minutes: 60 - # run: | - # npm run test:live - # working-directory: ts - # continue-on-error: true \ No newline at end of file From 8ee627a63e5f791054e7058f3314ca8c7cf57ead Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 3 Feb 2025 10:21:16 -0800 Subject: [PATCH 131/138] cleanup --- .github/workflows/smoke-tests.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index d1317690c..92587115a 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -9,8 +9,6 @@ on: workflow_dispatch: push: branches: ["main"] - # pull_request: - # branches: ["main"] pull_request_target: branches: ["main"] merge_group: @@ -27,7 +25,9 @@ permissions: env: NODE_OPTIONS: --max_old_space_size=8192 - DEBUG: pw:browser* + # DEBUG: pw:browser* # PlayWright debug messages + # ELECTRON_ENABLE_LOGGING: true # Electron debug messages + # DEBUG: typeagent:* # TypeAgent debug messages jobs: shell_and_cli: @@ -37,7 +37,6 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "windows-latest"] - #os: ["ubuntu-latest"] version: [20] runs-on: ${{ matrix.os }} From 1e8e3e0415aba741960b197a6d74cd916f6f513e Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 3 Feb 2025 10:21:33 -0800 Subject: [PATCH 132/138] deleted unused workflow --- .../smoke-tests-pull_request_targets.yml.bak | 121 ------------------ 1 file changed, 121 deletions(-) delete mode 100644 .github/workflows/smoke-tests-pull_request_targets.yml.bak diff --git a/.github/workflows/smoke-tests-pull_request_targets.yml.bak b/.github/workflows/smoke-tests-pull_request_targets.yml.bak deleted file mode 100644 index d336a2ee3..000000000 --- a/.github/workflows/smoke-tests-pull_request_targets.yml.bak +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -# This workflow runs live/smoke sanity tests - -name: smoke-tests - -on: - workflow_dispatch: - pull_request_target: - branches: ["main"] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -permissions: - pull-requests: read - contents: read - id-token: write - -env: - NODE_OPTIONS: --max_old_space_size=8192 - -jobs: - shell_and_cli: - environment: development-forks # required for federated credentials - strategy: - fail-fast: false - matrix: - #os: ["ubuntu-latest", "windows-latest", "macos-latest"] - os: ["ubuntu-latest"] - version: [20] - - runs-on: ${{ matrix.os }} - steps: - - if: runner.os == 'Linux' - run: | - sudo apt install libsecret-1-0 - - - name: Setup Git LF - run: | - git config --global core.autocrlf false - - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - uses: dorny/paths-filter@v3 - id: filter - with: - filters: | - ts: - - "ts/**" - - ".github/workflows/build-ts.yml" - - - uses: pnpm/action-setup@v4 - name: Install pnpm - with: - package_json_file: ts/package.json - - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.version }} - cache: "pnpm" - cache-dependency-path: ts/pnpm-lock.yaml - - name: Install dependencies - working-directory: ts - run: | - pnpm install --frozen-lockfile --strict-peer-dependencies - - - name: Install Playwright Browsers - run: pnpm exec playwright install --with-deps - working-directory: ts/packages/shell - - - name: Build - working-directory: ts - run: | - npm run build - - - name: Login to Azure - uses: azure/login@v2.2.0 - with: - client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_5B0D2D6BA40F4710B45721D2112356DD }} - tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_39BB903136F14B6EAD8F53A8AB78E3AA }} - subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_F36C1F2C4B2C49CA8DD5C52FAB98FA30 }} - - - name: Get Keys - run: | - node tools/scripts/getKeys.mjs --vault build-pipeline-kv - working-directory: ts - - - name: Test CLI - smoke - run: | - npm run start:dev 'prompt' 'why is the sky blue' - working-directory: ts/packages/cli - continue-on-error: true - - - name: Shell Tests - smoke (windows) - if: ${{ runner.os == 'windows' }} - timeout-minutes: 60 - run: | - npx playwright test simple.spec.ts - rm ../../.env - - - name: Shell Tests - smoke (linux) - if: ${{ runner.os == 'Linux' }} - timeout-minutes: 60 - run: | - Xvfb :99 -screen 0 1600x1200x24 & export DISPLAY=:99 - npx playwright test simple.spec.ts - rm ../../.env - working-directory: ts/packages/shell - continue-on-error: true - - - name: Live Tests - if: ${{ runner.os == 'linux' }} - timeout-minutes: 60 - run: | - npm run test:live - working-directory: ts - continue-on-error: true \ No newline at end of file From c1c41c19a29d0f33cdb682f94c9af1bc6788a111 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 3 Feb 2025 10:23:30 -0800 Subject: [PATCH 133/138] small refactor --- .github/workflows/shell-tests.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index e0ce15004..afcd35652 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -98,7 +98,6 @@ jobs: timeout-minutes: 60 run: | npm run shell:test - rm ../../.env working-directory: ts/packages/shell continue-on-error: true @@ -110,7 +109,6 @@ jobs: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 Xvfb :99 -screen 0 1600x1200x24 & export DISPLAY=:99 npm run shell:test - rm ../../.env working-directory: ts/packages/shell continue-on-error: true @@ -129,3 +127,9 @@ jobs: path: ts/packages/shell/playwright-report/ overwrite: true retention-days: 30 + + - name: Clean up Keys + run: | + rm ./.env + working-directory: ts + if: always() From 80595c4b3bfa307eebf6d715752f5e3f3b2e85e5 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 3 Feb 2025 11:48:15 -0800 Subject: [PATCH 134/138] list agent tests working --- ts/packages/shell/test/listAgent.spec.ts | 40 ++++++++++++---------- ts/packages/shell/test/testHelper.ts | 42 ++++++++++++------------ 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/ts/packages/shell/test/listAgent.spec.ts b/ts/packages/shell/test/listAgent.spec.ts index 23693cdac..76c139bb4 100644 --- a/ts/packages/shell/test/listAgent.spec.ts +++ b/ts/packages/shell/test/listAgent.spec.ts @@ -22,39 +22,43 @@ test.describe.configure({ mode: "serial" }); test.describe("List Agent Tests", () => { - test("create_update_list", async ({}, testInfo) => { + test("create_update_clear_list", async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); await testUserRequest( [ "create a shopping list", "what's on the shopping list?", - "add eggs, milk, flower to the shopping list", + "add eggs, milk, flour to the shopping list", "what's on the shopping list?", - "remove milk from the grocery list", + "remove milk from the shopping list", + "what's on the shopping list?", + "clear the shopping list", "what's on the shopping list?" ], [ "Created list: shopping", "List 'shopping' is empty", "Added items: eggs,milk,flour to list shopping", - "eggs\nmilk\nfoour", - "Removed items: milk from list grocery", - "eggs\nflour" + "eggs\nmilk\nflour", + "Removed items: milk from list shopping", + "eggs\nflour", + "Cleared list: shopping", + "List 'shopping' is empty", ]); }); - test("delete_list", async ({}, testInfo) => { - console.log(`Running test '${testInfo.title}`); + // test("delete_list", async ({}, testInfo) => { + // console.log(`Running test '${testInfo.title}`); - await testUserRequest( - [ - "delete shopping list", - "is there a shopping list?" - ], - [ - "Cleared list: shopping", - "List 'shopping' is empty" - ]); - }); + // await testUserRequest( + // [ + // "delete the shopping list", + // "is there a shopping list?" + // ], + // [ + // "Cleared list: shopping", + // "List 'shopping' is empty" + // ]); + // }); }); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index b66c87573..4b42e5049 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -175,8 +175,8 @@ export async function sendUserRequest(prompt: string, page: Page) { */ export async function sendUserRequestFast(prompt: string, page: Page) { const locator: Locator = page.locator("#phraseDiv"); - await locator.waitFor({ timeout: 120000, state: "visible" }); - await locator.fill(prompt, { timeout: 30000 }); + await locator.waitFor({ timeout: 5000, state: "visible" }); + await locator.fill(prompt, { timeout: 5000 }); page.keyboard.down("Enter"); } @@ -195,7 +195,7 @@ export async function sendUserRequestAndWaitForResponse( page: Page, ): Promise { const locators: Locator[] = await page - .locator(".chat-message-agent-text") + .locator(".chat-message-agent .chat-message-content") .all(); // send the user request @@ -220,7 +220,7 @@ export async function sendUserRequestAndWaitForResponse( export async function sendUserRequestAndWaitForCompletion(prompt: string, page: Page, expectedNumberOfAgentMessages: number = 1): Promise { // TODO: implement const locators: Locator[] = await page - .locator(".chat-message-agent-text") + .locator(".chat-message-agent .chat-message-content") .all(); // send the user request @@ -239,7 +239,7 @@ export async function sendUserRequestAndWaitForCompletion(prompt: string, page: */ export async function getLastAgentMessageText(page: Page): Promise { const locators: Locator[] = await page - .locator(".chat-message-agent-text") + .locator(".chat-message-agent .chat-message-content") .all(); return locators[0].innerText(); @@ -264,21 +264,16 @@ export async function getLastAgentMessage(page: Page): Promise { */ export async function isMessageCompleted(msg: Locator): Promise { // Agent message is complete once the metrics have been reported - - let timeWaited = 0; - - do { - try { - const details: Locator = await msg.locator(".metrics-details", { hasText: "Total" }); - - if (await details.count() > 0) { - return true; - } - - } catch (e) { - // not found + try { + const details: Locator = await msg.locator(".metrics-details", { hasText: "Total" }); + + if (await details.count() > 0) { + return true; } - } while (timeWaited < 30000); + + } catch (e) { + // not found + } return false; } @@ -377,6 +372,11 @@ export async function runTestCalback(callback: TestCallback): Promise { * shell. Test code executes between them. */ export async function testUserRequest(userRequests: string[], expectedResponses: string[]): Promise { + + if (userRequests.length != expectedResponses.length) { + throw new Error("Request/Response count mismatch!"); + } + // launch the app const mainWindow: Page = await startShell(); @@ -390,11 +390,11 @@ export async function testUserRequest(userRequests: string[], expectedResponses: // verify expected result expect( - msg.toLowerCase(), + msg, `Chat agent didn't respond with the expected message. Request: '${userRequests[i]}', Response: '${expectedResponses[i]}'`, ).toBe(expectedResponses[i]); } // close the application - exitApplication(mainWindow); + await exitApplication(mainWindow); } From 1ed6f0130854202f8629b3b5aed40168dbbe0f3d Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 3 Feb 2025 11:51:17 -0800 Subject: [PATCH 135/138] ran prettier --- ts/packages/shell/test/listAgent.spec.ts | 24 ++++----- ts/packages/shell/test/simple.spec.ts | 4 +- ts/packages/shell/test/testHelper.ts | 64 ++++++++++++++---------- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/ts/packages/shell/test/listAgent.spec.ts b/ts/packages/shell/test/listAgent.spec.ts index 76c139bb4..6cb4bcd48 100644 --- a/ts/packages/shell/test/listAgent.spec.ts +++ b/ts/packages/shell/test/listAgent.spec.ts @@ -14,38 +14,38 @@ import { sendUserRequestAndWaitForCompletion, getLaunchArgs, startShell, - testUserRequest + testUserRequest, } from "./testHelper"; // Annotate entire file as serial. test.describe.configure({ mode: "serial" }); test.describe("List Agent Tests", () => { - test("create_update_clear_list", async ({}, testInfo) => { console.log(`Running test '${testInfo.title}`); await testUserRequest( [ - "create a shopping list", + "create a shopping list", "what's on the shopping list?", "add eggs, milk, flour to the shopping list", "what's on the shopping list?", "remove milk from the shopping list", "what's on the shopping list?", - "clear the shopping list", - "what's on the shopping list?" - ], + "clear the shopping list", + "what's on the shopping list?", + ], [ - "Created list: shopping", + "Created list: shopping", "List 'shopping' is empty", "Added items: eggs,milk,flour to list shopping", "eggs\nmilk\nflour", "Removed items: milk from list shopping", "eggs\nflour", - "Cleared list: shopping", + "Cleared list: shopping", "List 'shopping' is empty", - ]); + ], + ); }); // test("delete_list", async ({}, testInfo) => { @@ -53,11 +53,11 @@ test.describe("List Agent Tests", () => { // await testUserRequest( // [ - // "delete the shopping list", + // "delete the shopping list", // "is there a shopping list?" - // ], + // ], // [ - // "Cleared list: shopping", + // "Cleared list: shopping", // "List 'shopping' is empty" // ]); // }); diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts index 1edca2c4b..bcbe12e6c 100644 --- a/ts/packages/shell/test/simple.spec.ts +++ b/ts/packages/shell/test/simple.spec.ts @@ -40,7 +40,7 @@ test("why is the sky blue?", { tag: "@smoke" }, async ({}, testInfo) => { const msg = await sendUserRequestAndWaitForCompletion( `why is the sky blue?`, mainWindow, - 1 + 1, ); expect( @@ -50,4 +50,4 @@ test("why is the sky blue?", { tag: "@smoke" }, async ({}, testInfo) => { // close the application await exitApplication(mainWindow); -}); \ No newline at end of file +}); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 4b42e5049..ecf3834ae 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -21,7 +21,9 @@ const runningApplications: Map = new Map< /** * Starts the electron app and returns the main page after the greeting agent message has been posted. */ -export async function startShell(waitForAgentGreeting: boolean = true): Promise { +export async function startShell( + waitForAgentGreeting: boolean = true, +): Promise { // this is needed to isolate these tests session from other concurrently running tests process.env["INSTANCE_NAME"] = `test_${process.env["TEST_WORKER_INDEX"]}_${process.env["TEST_PARALLEL_INDEX"]}`; @@ -182,11 +184,11 @@ export async function sendUserRequestFast(prompt: string, page: Page) { /** * Submits a user request to the system via the chat input box and then waits for the first available response - * NOTE: If your expected response changes or you invoke multi-action flow you should be calling + * NOTE: If your expected response changes or you invoke multi-action flow you should be calling * sendUserRequestAndAwaitSpecificResponse() instead of this call - * + * * Remarks: Use this method when calling @commands...agent calls should use aforementioned function. - * + * * @param prompt The user request/prompt. * @param page The main page from the electron host application. */ @@ -210,18 +212,22 @@ export async function sendUserRequestAndWaitForResponse( /** * Submits a user request and awaits for completion of the response. - * - * Remarks: Call this function when expecting an agent action response. - * + * + * Remarks: Call this function when expecting an agent action response. + * * @param prompt The user request/prompt. * @param page The page hosting the user shell * @param expectedNumberOfAgentMessages The number of expected agent messages to wait for/receive */ -export async function sendUserRequestAndWaitForCompletion(prompt: string, page: Page, expectedNumberOfAgentMessages: number = 1): Promise { +export async function sendUserRequestAndWaitForCompletion( + prompt: string, + page: Page, + expectedNumberOfAgentMessages: number = 1, +): Promise { // TODO: implement const locators: Locator[] = await page - .locator(".chat-message-agent .chat-message-content") - .all(); + .locator(".chat-message-agent .chat-message-content") + .all(); // send the user request await sendUserRequest(prompt, page); @@ -258,19 +264,20 @@ export async function getLastAgentMessage(page: Page): Promise { } /** - * Determines if the supplied agent message/action has been completed - * + * Determines if the supplied agent message/action has been completed + * * @param msg The agent message to check for completion */ export async function isMessageCompleted(msg: Locator): Promise { // Agent message is complete once the metrics have been reported try { - const details: Locator = await msg.locator(".metrics-details", { hasText: "Total" }); - - if (await details.count() > 0) { + const details: Locator = await msg.locator(".metrics-details", { + hasText: "Total", + }); + + if ((await details.count()) > 0) { return true; } - } catch (e) { // not found } @@ -285,9 +292,9 @@ export async function isMessageCompleted(msg: Locator): Promise { * @param expectedMessageCount The expected # of agent messages at this time. * @param waitForMessageCompletion A flag indicating if we should block util the message is completed. * @param ignore A list of messges that this method will consider noise and will reject as false positivies - * i.e. [".."] and this method will ignore agent messages that are "..." and will continue waiting. + * i.e. [".."] and this method will ignore agent messages that are "..." and will continue waiting. * This is useful when an agent sends status messages. - * + * * @returns When the expected # of messages is reached or the timeout is reached. Whichever occurrs first. */ export async function waitForAgentMessage( @@ -295,7 +302,7 @@ export async function waitForAgentMessage( timeout: number, expectedMessageCount: number, waitForMessageCompletion: boolean = false, - ignore: string[] = [] + ignore: string[] = [], ): Promise { let timeWaited = 0; let locators: Locator[] = await page @@ -303,8 +310,12 @@ export async function waitForAgentMessage( .all(); let originalAgentMessageCount = locators.length; let messageCount = originalAgentMessageCount; - - if (expectedMessageCount == messageCount && (!waitForMessageCompletion || await isMessageCompleted(await getLastAgentMessage(page)))) { + + if ( + expectedMessageCount == messageCount && + (!waitForMessageCompletion || + (await isMessageCompleted(await getLastAgentMessage(page)))) + ) { return; } @@ -323,7 +334,6 @@ export async function waitForAgentMessage( messageCount = originalAgentMessageCount; } } - } while ( timeWaited <= timeout && messageCount == originalAgentMessageCount @@ -356,7 +366,7 @@ export type TestCallback = () => void; * Encapsulates the supplied method within a startup and shutdown of teh * shell. Test code executes between them. */ -export async function runTestCalback(callback: TestCallback): Promise { +export async function runTestCalback(callback: TestCallback): Promise { // launch the app const mainWindow: Page = await startShell(); @@ -371,8 +381,10 @@ export async function runTestCalback(callback: TestCallback): Promise { * Encapsulates the supplied method within a startup and shutdown of teh * shell. Test code executes between them. */ -export async function testUserRequest(userRequests: string[], expectedResponses: string[]): Promise { - +export async function testUserRequest( + userRequests: string[], + expectedResponses: string[], +): Promise { if (userRequests.length != expectedResponses.length) { throw new Error("Request/Response count mismatch!"); } @@ -385,7 +397,7 @@ export async function testUserRequest(userRequests: string[], expectedResponses: const msg = await sendUserRequestAndWaitForCompletion( userRequests[i], mainWindow, - 1 + 1, ); // verify expected result From 1f0aa038d24306bd19dfd75674a4a6703bf73003 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 3 Feb 2025 11:54:26 -0800 Subject: [PATCH 136/138] re-enabled linux shell tests --- .github/workflows/shell-tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index afcd35652..6a1c1cf4e 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -28,7 +28,7 @@ jobs: strategy: fail-fast: false matrix: - os: ["windows-latest"] + os: ["windows-latest", "ubuntu-latest"] version: [20] runs-on: ${{ matrix.os }} @@ -104,9 +104,7 @@ jobs: - name: Shell Tests (linux) if: ${{ runner.os == 'Linux' }} timeout-minutes: 60 - # https://github.com/microsoft/playwright/issues/34251 - sysctl command run: | - sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 Xvfb :99 -screen 0 1600x1200x24 & export DISPLAY=:99 npm run shell:test working-directory: ts/packages/shell From 471f8031aa6e28d3b6b1a562ec4661dc37df45dd Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 3 Feb 2025 14:10:21 -0800 Subject: [PATCH 137/138] fixed session tests --- .../shell/test/sessionCommands.spec.ts | 34 +++++++++---------- ts/packages/shell/test/simple.spec.ts | 1 - ts/packages/shell/test/testHelper.ts | 20 +++++------ 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/ts/packages/shell/test/sessionCommands.spec.ts b/ts/packages/shell/test/sessionCommands.spec.ts index 748ab73d2..fa8967e0b 100644 --- a/ts/packages/shell/test/sessionCommands.spec.ts +++ b/ts/packages/shell/test/sessionCommands.spec.ts @@ -12,7 +12,7 @@ import { getAppPath, getLastAgentMessage, sendUserRequest, - sendUserRequestAndWaitForResponse, + sendUserRequestAndWaitForCompletion, startShell, waitForAgentMessage, } from "./testHelper"; @@ -29,20 +29,20 @@ test.describe("@session Commands", () => { const mainWindow: Page = await startShell(); // get the session count - let msg = await sendUserRequestAndWaitForResponse( + let msg = await sendUserRequestAndWaitForCompletion( `@session list`, mainWindow, ); const sessions: string[] = msg.split("\n"); - msg = await sendUserRequestAndWaitForResponse( + msg = await sendUserRequestAndWaitForCompletion( `@session new`, mainWindow, ); expect(msg.toLowerCase()).toContain("new session created: "); - msg = await sendUserRequestAndWaitForResponse( + msg = await sendUserRequestAndWaitForCompletion( `@session list`, mainWindow, ); @@ -52,7 +52,7 @@ test.describe("@session Commands", () => { sessions.length + 1, ); - msg = await sendUserRequestAndWaitForResponse(`@history`, mainWindow); + msg = await sendUserRequestAndWaitForCompletion(`@history`, mainWindow); expect(msg.length, "History NOT cleared!").toBe(0); // close the application @@ -66,14 +66,14 @@ test.describe("@session Commands", () => { const mainWindow: Page = await startShell(); // create a new session so we have at least two - let msg = await sendUserRequestAndWaitForResponse( + let msg = await sendUserRequestAndWaitForCompletion( `@session new`, mainWindow, ); expect(msg.toLowerCase()).toContain("new session created: "); // get the session count - msg = await sendUserRequestAndWaitForResponse( + msg = await sendUserRequestAndWaitForCompletion( `@session list`, mainWindow, ); @@ -82,7 +82,7 @@ test.describe("@session Commands", () => { const sessionName: string = sessions[sessions.length - 1]; // issue delete session command - msg = await sendUserRequestAndWaitForResponse( + msg = await sendUserRequestAndWaitForCompletion( `@session delete ${sessions[0]}`, mainWindow, ); @@ -92,7 +92,7 @@ test.describe("@session Commands", () => { await mainWindow.locator(".choice-button", { hasText: "No" }).click(); // verify session not deleted - msg = await sendUserRequestAndWaitForResponse( + msg = await sendUserRequestAndWaitForCompletion( `@session list`, mainWindow, ); @@ -102,7 +102,7 @@ test.describe("@session Commands", () => { ); // reissue delete session command - msg = await sendUserRequestAndWaitForResponse( + msg = await sendUserRequestAndWaitForCompletion( `@session delete ${sessions[0]}`, mainWindow, ); @@ -112,7 +112,7 @@ test.describe("@session Commands", () => { await mainWindow.locator(".choice-button", { hasText: "Yes" }).click(); // get new session count - msg = await sendUserRequestAndWaitForResponse( + msg = await sendUserRequestAndWaitForCompletion( `@session list`, mainWindow, ); @@ -122,7 +122,7 @@ test.describe("@session Commands", () => { ); // get session info - msg = await sendUserRequestAndWaitForResponse( + msg = await sendUserRequestAndWaitForCompletion( `@session info`, mainWindow, ); @@ -140,14 +140,14 @@ test.describe("@session Commands", () => { const mainWindow: Page = await startShell(); // reset - let msg = await sendUserRequestAndWaitForResponse( + let msg = await sendUserRequestAndWaitForCompletion( `@session reset`, mainWindow, ); expect(msg).toContain("Session settings revert to default."); // issue clear session command - msg = await sendUserRequestAndWaitForResponse( + msg = await sendUserRequestAndWaitForCompletion( `@session clear`, mainWindow, ); @@ -167,21 +167,21 @@ test.describe("@session Commands", () => { const mainWindow: Page = await startShell(); // create a new session - let msg = await sendUserRequestAndWaitForResponse( + let msg = await sendUserRequestAndWaitForCompletion( `@session new`, mainWindow, ); expect(msg.toLowerCase()).toContain("new session created: "); // get the session list - msg = await sendUserRequestAndWaitForResponse( + msg = await sendUserRequestAndWaitForCompletion( `@session list`, mainWindow, ); const sessions: string[] = msg.split("\n"); // open the earlier session - msg = await sendUserRequestAndWaitForResponse( + msg = await sendUserRequestAndWaitForCompletion( `@session open ${sessions[0]}`, mainWindow, ); diff --git a/ts/packages/shell/test/simple.spec.ts b/ts/packages/shell/test/simple.spec.ts index bcbe12e6c..613a1bea6 100644 --- a/ts/packages/shell/test/simple.spec.ts +++ b/ts/packages/shell/test/simple.spec.ts @@ -40,7 +40,6 @@ test("why is the sky blue?", { tag: "@smoke" }, async ({}, testInfo) => { const msg = await sendUserRequestAndWaitForCompletion( `why is the sky blue?`, mainWindow, - 1, ); expect( diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index ecf3834ae..75ab66fef 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -217,12 +217,10 @@ export async function sendUserRequestAndWaitForResponse( * * @param prompt The user request/prompt. * @param page The page hosting the user shell - * @param expectedNumberOfAgentMessages The number of expected agent messages to wait for/receive */ export async function sendUserRequestAndWaitForCompletion( prompt: string, page: Page, - expectedNumberOfAgentMessages: number = 1, ): Promise { // TODO: implement const locators: Locator[] = await page @@ -248,7 +246,7 @@ export async function getLastAgentMessageText(page: Page): Promise { .locator(".chat-message-agent .chat-message-content") .all(); - return locators[0].innerText(); + return await locators[0].innerText(); } /** @@ -311,15 +309,15 @@ export async function waitForAgentMessage( let originalAgentMessageCount = locators.length; let messageCount = originalAgentMessageCount; - if ( - expectedMessageCount == messageCount && - (!waitForMessageCompletion || - (await isMessageCompleted(await getLastAgentMessage(page)))) - ) { - return; - } - do { + if ( + expectedMessageCount == messageCount && + (!waitForMessageCompletion || + (await isMessageCompleted(await getLastAgentMessage(page)))) + ) { + return; + } + await page.waitForTimeout(1000); timeWaited += 1000; From 7eafd7967ae56e1d1cbe8fecd159a1318a9809e6 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 3 Feb 2025 14:11:34 -0800 Subject: [PATCH 138/138] ran prettier --- ts/packages/shell/test/testHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 75ab66fef..c470888ad 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -317,7 +317,7 @@ export async function waitForAgentMessage( ) { return; } - + await page.waitForTimeout(1000); timeWaited += 1000;