diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b9e89b..56da81e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -121,7 +121,7 @@ xvfb-run -a yarn test:webos xvfb-run -a -s '-screen 0 1920x1080x24' yarn test:webos ``` -The fixture starts the extracted Simulator executable directly with `--ozone-platform=x11` and `--no-sandbox`. It connects the installed Playwright package through `chromium.connectOverCDP()`. It selects the exact `file://` application target. It installs fixture interception before the tested reload. DNS resolution stays disabled, and unexpected external requests fail the test. +The fixture starts the extracted Simulator executable directly with `--ozone-platform=x11` and `--no-sandbox`. It connects the installed Playwright package through `chromium.connectOverCDP()`. It waits for the exact `file://` application target to initialize. Then it installs fixture interception before the tested reload. DNS resolution stays disabled, and unexpected external requests fail the test. Each test gets a separate Simulator storage directory. Restart tests reuse that directory. Navigation uses CDP input, including webOS Back keycode `461`. The reports contain application screenshots, Simulator output, console errors, target details, Chromium versions, launch arguments, HTML, and JUnit. CI retains them for 14 days. The fixture stops the Simulator process group after each test. CI also stops remaining Simulator processes after a failure. diff --git a/tests/webos/simulator.fixture.ts b/tests/webos/simulator.fixture.ts index 00ecf60..8002614 100644 --- a/tests/webos/simulator.fixture.ts +++ b/tests/webos/simulator.fixture.ts @@ -65,6 +65,10 @@ export class Simulator { this.browser = await chromium.connectOverCDP(endpoint, { timeout: 15_000 }); const context = this.browser.contexts()[0]; if (!context) throw new Error('Simulator has no default context.'); + // Interception can miss CDP targets that are still initializing. + await expect.poll(() => context.pages().filter(page => page.url() === appURL).length, + { timeout: 15_000, message: 'Simulator must create exactly one application target.' }).toBe(1); + this.page = context.pages().find(page => page.url() === appURL)!; await context.route('**/*', async route => { const request = route.request(); if ([path.resolve('dist'), path.dirname(executable)].some(directory => @@ -106,9 +110,6 @@ export class Simulator { } await route.fulfill({ ...response, headers }); }); - await expect.poll(() => context.pages().filter(page => page.url() === appURL).length, - { timeout: 15_000, message: 'Simulator must create exactly one application target.' }).toBe(1); - this.page = context.pages().find(page => page.url() === appURL)!; this.page.on('pageerror', error => this.errors.push(error.message)); this.page.on('console', message => { if (message.type() === 'error') this.consoleErrors.push(message.text()); }); this.input = await context.newCDPSession(this.page); diff --git a/tests/webos/startup.spec.ts b/tests/webos/startup.spec.ts new file mode 100644 index 0000000..5df7052 --- /dev/null +++ b/tests/webos/startup.spec.ts @@ -0,0 +1,30 @@ +import { chromium, type Browser } from '@playwright/test'; +import { test, expect } from './simulator.fixture'; + +const connect = chromium.connectOverCDP; + +test.beforeAll(() => { + chromium.connectOverCDP = new Proxy(connect, { + async apply(target, receiver, args) { + const browser: Browser = await Reflect.apply(target, receiver, args); + const context = browser.contexts()[0]; + const route = context.route.bind(context); + // Delay routing to reproduce the application target initialization race. + context.route = async (...args: Parameters) => { + await new Promise(resolve => setTimeout(resolve, 500)); + return route(...args); + }; + return browser; + }, + }); +}); + +test.afterAll(() => { chromium.connectOverCDP = connect; }); + +test('startup: fixtures work when interception is delayed', async ({ simulator }) => { + const page = simulator.page; + await expect(page.locator('#boot')).toHaveCount(0); + await expect(page.locator('.carouselTitle').first()).toHaveText('Fixture conference 0'); + await simulator.key('ArrowRight'); + await expect(page.locator('.embla__slide.active .embla__slide__title')).toHaveText('Other talk 0'); +});