Trace Viewer uses Service Workers to show traces. To view trace:
diff --git a/packages/web/src/uiUtils.ts b/packages/web/src/uiUtils.ts
index a0b7a59c36..cded19af20 100644
--- a/packages/web/src/uiUtils.ts
+++ b/packages/web/src/uiUtils.ts
@@ -248,3 +248,13 @@ export function useFlash(): [boolean, EffectCallback] {
}, [setFlash]);
return [flash, trigger];
}
+
+export function useCookies() {
+ const cookies = React.useMemo(() => {
+ return document.cookie.split('; ').filter(v => v.includes('=')).map(kv => {
+ const separator = kv.indexOf('=');
+ return [kv.substring(0, separator), kv.substring(separator + 1)];
+ });
+ }, []);
+ return cookies;
+}
diff --git a/tests/library/browsercontext-fetch.spec.ts b/tests/library/browsercontext-fetch.spec.ts
index f23b59f97c..ff0c1e86a8 100644
--- a/tests/library/browsercontext-fetch.spec.ts
+++ b/tests/library/browsercontext-fetch.spec.ts
@@ -280,7 +280,8 @@ it('should add cookies from Set-Cookie header', async ({ context, page, server }
expect((await page.evaluate(() => document.cookie)).split(';').map(s => s.trim()).sort()).toEqual(['foo=bar', 'session=value']);
});
-it('should preserve cookie order from Set-Cookie header', async ({ context, page, server }) => {
+it('should preserve cookie order from Set-Cookie header', async ({ context, page, server, browserName, isLinux }) => {
+ it.fixme(browserName === 'webkit' && isLinux, 'https://github.com/microsoft/playwright-browsers/issues/1512');
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23390' });
server.setRoute('/setcookie.html', (req, res) => {
res.setHeader('Set-Cookie', ['cookie.0=foo', 'cookie.1=bar']);
diff --git a/tests/library/events/check-listener-leaks.spec.ts b/tests/library/events/check-listener-leaks.spec.ts
index 5a328d720f..916c5678f2 100644
--- a/tests/library/events/check-listener-leaks.spec.ts
+++ b/tests/library/events/check-listener-leaks.spec.ts
@@ -22,7 +22,7 @@
import events from 'events';
import { EventEmitter } from '../../../packages/playwright-core/lib/client/eventEmitter';
-import { setUnderTest } from '../../../packages/playwright-core/lib/utils/isomorphic/debug';
+import { setUnderTest } from '../../../packages/playwright-core/lib/server/utils/debug';
import { test, expect } from '@playwright/test';
import * as common from './utils';
diff --git a/tests/library/modernizr.spec.ts b/tests/library/modernizr.spec.ts
index 0a1b124301..5505e387c1 100644
--- a/tests/library/modernizr.spec.ts
+++ b/tests/library/modernizr.spec.ts
@@ -66,7 +66,6 @@ it('Safari Desktop', async ({ browser, browserName, platform, server, headless }
}
if (platform === 'win32') {
- expected.datalistelem = false;
expected.getusermedia = false;
expected.peerconnection = false;
expected.speechrecognition = false;
@@ -75,7 +74,6 @@ it('Safari Desktop', async ({ browser, browserName, platform, server, headless }
expected.webaudio = false;
expected.gamepads = false;
- expected.input.list = false;
delete expected.datalistelem;
expected.publickeycredential = false;
@@ -84,8 +82,6 @@ it('Safari Desktop', async ({ browser, browserName, platform, server, headless }
expected.datachannel = false;
expected.inputtypes.color = false;
- expected.inputtypes.month = false;
- expected.inputtypes.week = false;
expected.inputtypes.date = false;
expected.inputtypes['datetime-local'] = false;
expected.inputtypes.time = false;
@@ -133,7 +129,6 @@ it('Mobile Safari', async ({ playwright, browser, browserName, platform, server,
}
if (platform === 'win32') {
- expected.datalistelem = false;
expected.getusermedia = false;
expected.peerconnection = false;
expected.speechrecognition = false;
@@ -142,8 +137,6 @@ it('Mobile Safari', async ({ playwright, browser, browserName, platform, server,
expected.webaudio = false;
expected.gamepads = false;
- expected.input.list = false;
-
delete expected.datalistelem;
expected.publickeycredential = false;
@@ -152,8 +145,6 @@ it('Mobile Safari', async ({ playwright, browser, browserName, platform, server,
expected.datachannel = false;
expected.inputtypes.color = false;
- expected.inputtypes.month = false;
- expected.inputtypes.week = false;
expected.inputtypes.date = false;
expected.inputtypes['datetime-local'] = false;
expected.inputtypes.time = false;
diff --git a/tests/library/unit/sequence.spec.ts b/tests/library/unit/sequence.spec.ts
index e0decd4d84..7fdb20bd1a 100644
--- a/tests/library/unit/sequence.spec.ts
+++ b/tests/library/unit/sequence.spec.ts
@@ -16,7 +16,7 @@
import { test as it, expect } from '@playwright/test';
-import { findRepeatedSubsequences } from '../../../packages/playwright-core/lib/utils/isomorphic/sequence';
+import { findRepeatedSubsequencesForTest as findRepeatedSubsequences } from '../../../packages/playwright-core/lib/server/callLog';
it('should return an empty array when the input is empty', () => {
const input = [];
diff --git a/tests/playwright-test/playwright-test-fixtures.ts b/tests/playwright-test/playwright-test-fixtures.ts
index 789ed1feb6..43503497d2 100644
--- a/tests/playwright-test/playwright-test-fixtures.ts
+++ b/tests/playwright-test/playwright-test-fixtures.ts
@@ -227,6 +227,7 @@ export function cleanEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
GITHUB_RUN_ID: undefined,
GITHUB_SERVER_URL: undefined,
GITHUB_SHA: undefined,
+ GITHUB_EVENT_PATH: undefined,
// END: Reserved CI
PW_TEST_HTML_REPORT_OPEN: undefined,
PLAYWRIGHT_HTML_OPEN: undefined,
diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts
index bf88f52ade..78ef741415 100644
--- a/tests/playwright-test/reporter-html.spec.ts
+++ b/tests/playwright-test/reporter-html.spec.ts
@@ -1221,11 +1221,8 @@ for (const useIntermediateMergeReport of [true, false] as const) {
const result = await runInlineTest(files, { reporter: 'dot,html' }, {
PLAYWRIGHT_HTML_OPEN: 'never',
GITHUB_REPOSITORY: 'microsoft/playwright-example-for-test',
- GITHUB_RUN_ID: 'example-run-id',
GITHUB_SERVER_URL: 'https://playwright.dev',
GITHUB_SHA: 'example-sha',
- GITHUB_REF_NAME: '42/merge',
- GITHUB_BASE_REF: 'HEAD~1',
});
await showReport();
@@ -1235,9 +1232,69 @@ for (const useIntermediateMergeReport of [true, false] as const) {
await expect(page.locator('.metadata-view')).toMatchAriaSnapshot(`
- 'link "chore(html): make this test look nice"'
- text: /^William on/
+ - link /^[a-f0-9]{7}$/
+ - text: 'foo : value1 bar : {"prop":"value2"} baz : ["value3",123]'
+ `);
+ });
+
+ test('should include metadata with populateGitInfo on GHA', async ({ runInlineTest, writeFiles, showReport, page }) => {
+ const files = {
+ 'uncommitted.txt': `uncommitted file`,
+ 'playwright.config.ts': `
+ export default {
+ populateGitInfo: true,
+ metadata: { foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] }
+ };
+ `,
+ 'example.spec.ts': `
+ import { test, expect } from '@playwright/test';
+ test('sample', async ({}) => { expect(2).toBe(2); });
+ `,
+ };
+ const baseDir = await writeFiles(files);
+
+ const execGit = async (args: string[]) => {
+ const { code, stdout, stderr } = await spawnAsync('git', args, { stdio: 'pipe', cwd: baseDir });
+ if (!!code)
+ throw new Error(`Non-zero exit of:\n$ git ${args.join(' ')}\nConsole:\nstdout:\n${stdout}\n\nstderr:\n${stderr}\n\n`);
+ return;
+ };
+
+ await execGit(['init']);
+ await execGit(['config', '--local', 'user.email', 'shakespeare@example.local']);
+ await execGit(['config', '--local', 'user.name', 'William']);
+ await execGit(['add', 'playwright.config.ts']);
+ await execGit(['commit', '-m', 'init']);
+ await execGit(['add', '*.ts']);
+ await execGit(['commit', '-m', 'chore(html): make this test look nice']);
+
+ const eventPath = path.join(baseDir, 'event.json');
+ await fs.promises.writeFile(eventPath, JSON.stringify({
+ pull_request: {
+ title: 'My PR',
+ number: 42,
+ base: { ref: 'main' },
+ },
+ }));
+
+ const result = await runInlineTest(files, { reporter: 'dot,html' }, {
+ PLAYWRIGHT_HTML_OPEN: 'never',
+ GITHUB_REPOSITORY: 'microsoft/playwright-example-for-test',
+ GITHUB_RUN_ID: 'example-run-id',
+ GITHUB_SERVER_URL: 'https://playwright.dev',
+ GITHUB_SHA: 'example-sha',
+ GITHUB_EVENT_PATH: eventPath,
+ });
+
+ await showReport();
+
+ expect(result.exitCode).toBe(0);
+ await page.getByRole('button', { name: 'Metadata' }).click();
+ await expect(page.locator('.metadata-view')).toMatchAriaSnapshot(`
+ - 'link "My PR"'
+ - text: /^William on/
- link "Logs"
- link "Pull Request"
- - link /^[a-f0-9]{7}$/
- text: 'foo : value1 bar : {"prop":"value2"} baz : ["value3",123]'
`);
});
diff --git a/tests/playwright-test/ui-mode-llm.spec.ts b/tests/playwright-test/ui-mode-llm.spec.ts
new file mode 100644
index 0000000000..8c7b0447ec
--- /dev/null
+++ b/tests/playwright-test/ui-mode-llm.spec.ts
@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { test, expect, retries } from './ui-mode-fixtures';
+
+test.describe.configure({ mode: 'parallel', retries });
+
+test('openai', async ({ runUITest, server }) => {
+ server.setRoute('/v1/chat/completions', async (req, res) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ res.setHeader('Access-Control-Allow-Headers', '*');
+ if (req.method === 'OPTIONS')
+ return res.end();
+
+ expect(req.headers.authorization).toBe('Bearer fake-key');
+ expect((await req.postBody).toString()).toContain(`- button \\"Submit\\"`);
+ const event = {
+ object: 'chat.completion.chunk',
+ choices: [{ delta: { content: 'This is a mock response' } }]
+ };
+ res.setHeader('Content-Type', 'text/event-stream');
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
+ });
+
+ const { page } = await runUITest({
+ 'a.test.ts': `
+ import { test, expect } from '@playwright/test';
+ test('trace test', async ({ page }) => {
+ await page.setContent('');
+ expect(1).toBe(2);
+ });
+ `,
+ }, {
+ OPENAI_API_KEY: 'fake-key',
+ OPENAI_BASE_URL: server.PREFIX,
+ });
+
+ await page.getByTitle('Run all').click();
+ await page.getByText('Errors', { exact: true }).click();
+ await page.getByRole('button', { name: 'Fix with AI' }).click();
+ await expect(page.getByRole('tabpanel', { name: 'Errors' })).toMatchAriaSnapshot(`
+ - tabpanel "Errors":
+ - text: Help me with the error above. Take the page snapshot into account.
+ - text: This is a mock response
+ `);
+});
+
+test('anthropic', async ({ runUITest, server }) => {
+ server.setRoute('/v1/messages', async (req, res) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ res.setHeader('Access-Control-Allow-Headers', '*');
+ if (req.method === 'OPTIONS')
+ return res.end();
+
+ expect(req.headers['x-api-key']).toBe('fake-key');
+ expect((await req.postBody).toString()).toContain(`- button \\"Submit\\"`);
+ const event = {
+ type: 'content_block_delta',
+ delta: { text: 'This is a mock response' },
+ };
+ res.setHeader('Content-Type', 'text/event-stream');
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
+ });
+
+ const { page } = await runUITest({
+ 'a.test.ts': `
+ import { test, expect } from '@playwright/test';
+ test('trace test', async ({ page }) => {
+ await page.setContent('');
+ expect(1).toBe(2);
+ });
+ `,
+ }, {
+ ANTHROPIC_API_KEY: 'fake-key',
+ ANTHROPIC_BASE_URL: server.PREFIX,
+ });
+
+ await page.getByTitle('Run all').click();
+ await page.getByText('Errors', { exact: true }).click();
+ await page.getByRole('button', { name: 'Fix with AI' }).click();
+ await expect(page.getByRole('tabpanel', { name: 'Errors' })).toMatchAriaSnapshot(`
+ - tabpanel "Errors":
+ - text: Help me with the error above. Take the page snapshot into account.
+ - text: This is a mock response
+ `);
+});