Skip to content

Commit 4fa2a75

Browse files
committed
chore: introduce new extension test mode
1 parent c3c842c commit 4fa2a75

File tree

4 files changed

+155
-20
lines changed

4 files changed

+155
-20
lines changed

tests/config/traceViewerFixtures.ts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ class TraceViewerPage {
5757
this.actionTitles = page.locator('.action-title');
5858
this.actionsTree = page.getByTestId('actions-tree');
5959
this.callLines = page.locator('.call-tab .call-line');
60-
this.logLines = page.getByTestId('log-list').locator('.list-view-entry');
60+
this.logLines = page.getByRole('list', { name: 'Log entries' }).getByRole('listitem');
6161
this.consoleLines = page.locator('.console-line');
6262
this.consoleLineMessages = page.locator('.console-line-message');
6363
this.errorMessages = page.locator('.error-message');
6464
this.consoleStacks = page.locator('.console-stack');
65-
this.stackFrames = page.getByTestId('stack-trace-list').locator('.list-view-entry');
66-
this.networkRequests = page.getByTestId('network-list').locator('.list-view-entry');
65+
this.stackFrames = page.getByRole('list', { name: 'Stack trace' }).getByRole('listitem');
66+
this.networkRequests = page.getByRole('list', { name: 'Network requests' }).getByRole('listitem');
6767
this.snapshotContainer = page.locator('.snapshot-container iframe.snapshot-visible[name=snapshot]');
6868
this.metadataTab = page.getByTestId('metadata-view');
6969
this.sourceCodeTab = page.getByTestId('source-code');
@@ -73,58 +73,57 @@ class TraceViewerPage {
7373
this.displayCanvasContentSetting = page.locator('.setting').getByText('Display canvas content');
7474
}
7575

76-
async actionIconsText(action: string) {
77-
const entry = await this.page.waitForSelector(`.tree-view-entry:has-text("${action}")`);
78-
await entry.waitForSelector('.action-icon-value:visible');
79-
return await entry.$$eval('.action-icon-value:visible', ee => ee.map(e => e.textContent));
76+
actionIconsText(action: string) {
77+
const entry = this.page.locator(`.tree-view-entry`).filter({ hasText: action });
78+
return entry.locator('.action-icon-value').filter({ visible: true });
8079
}
8180

82-
async actionIcons(action: string) {
83-
return await this.page.waitForSelector(`.tree-view-entry:has-text("${action}") .action-icons`);
81+
actionIcons(action: string) {
82+
return this.page.locator(`.tree-view-entry`).filter({ hasText: action }).locator('.action-icons').filter({ visible: true });
8483
}
8584

8685
@step
87-
async expandAction(title: string, ordinal: number = 0) {
88-
await this.actionsTree.locator('.tree-view-entry', { hasText: title }).nth(ordinal).locator('.codicon-chevron-right').click();
86+
async expandAction(title: string) {
87+
await this.actionsTree.getByRole('treeitem', { name: title }).nth(0).getByRole('button', { name: 'Expand' }).click();
8988
}
9089

9190
@step
9291
async selectAction(title: string, ordinal: number = 0) {
93-
await this.page.locator(`.action-title:has-text("${title}")`).nth(ordinal).click();
92+
await this.actionsTree.getByRole('treeitem', { name: title }).nth(ordinal).click();
9493
}
9594

9695
@step
9796
async hoverAction(title: string, ordinal: number = 0) {
98-
await this.page.locator(`.action-title:has-text("${title}")`).nth(ordinal).hover();
97+
await this.actionTitles.filter({ hasText: title }).nth(ordinal).hover();
9998
}
10099

101100
@step
102101
async selectSnapshot(name: string) {
103-
await this.page.click(`.snapshot-tab .tabbed-pane-tab-label:has-text("${name}")`);
102+
await this.page.locator('.snapshot-tab .tabbed-pane-tab-label').filter({ hasText: name }).click();
104103
}
105104

106105
async showErrorsTab() {
107-
await this.page.click('text="Errors"');
106+
await this.page.getByRole('tab', { name: 'Errors' }).click();
108107
}
109108

110109
async showConsoleTab() {
111-
await this.page.click('text="Console"');
110+
await this.page.getByRole('tab', { name: 'Console' }).click();
112111
}
113112

114113
async showSourceTab() {
115-
await this.page.click('text="Source"');
114+
await this.page.getByRole('tab', { name: 'Source' }).click();
116115
}
117116

118117
async showNetworkTab() {
119-
await this.page.click('text="Network"');
118+
await this.page.getByRole('tab', { name: 'Network' }).click();
120119
}
121120

122121
async showMetadataTab() {
123-
await this.page.click('text="Metadata"');
122+
await this.page.getByRole('tab', { name: 'Metadata' }).click();
124123
}
125124

126125
async showSettings() {
127-
await this.page.locator('.settings-gear').click();
126+
await this.page.getByRole('button', { name: 'Settings' }).click();
128127
}
129128

130129
@step

tests/extension/extensionTest.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { baseTest } from '../config/baseTest';
18+
import { chromium } from 'playwright';
19+
import { expect, type PageTestFixtures, type PageWorkerFixtures } from '../page/pageTestApi';
20+
import type { TraceViewerFixtures } from '../config/traceViewerFixtures';
21+
import { traceViewerFixtures } from '../config/traceViewerFixtures';
22+
export { expect } from '@playwright/test';
23+
import http from 'node:http';
24+
import path from 'node:path';
25+
import { AddressInfo } from 'node:net';
26+
27+
export const extensionTest = baseTest.extend<TraceViewerFixtures>(traceViewerFixtures).extend<PageTestFixtures, PageWorkerFixtures>({
28+
browserVersion: [({ browser }) => browser.version(), { scope: 'worker' }],
29+
browserMajorVersion: [({ browserVersion }, use) => use(Number(browserVersion.split('.')[0])), { scope: 'worker' }],
30+
isAndroid: [false, { scope: 'worker' }],
31+
isElectron: [false, { scope: 'worker' }],
32+
electronMajorVersion: [0, { scope: 'worker' }],
33+
isWebView2: [false, { scope: 'worker' }],
34+
isHeadlessShell: [false, { scope: 'worker' }],
35+
36+
browser: [async ({ playwright }, use, testInfo) => {
37+
const httpServer = http.createServer();
38+
await new Promise<void>(resolve => httpServer.listen(0, resolve));
39+
const pathToExtension = path.join(__dirname, '../../../playwright-mcp/extension');
40+
const context = await chromium.launchPersistentContext('', {
41+
args: [
42+
`--disable-extensions-except=${pathToExtension}`,
43+
`--load-extension=${pathToExtension}`,
44+
'--enable-features=AllowContentInitiatedDataUrlNavigations',
45+
],
46+
channel: 'chromium',
47+
});
48+
const { CDPBridgeServer } = await import('../../../playwright-mcp/src/cdp-relay.ts');
49+
const server = new CDPBridgeServer(httpServer);
50+
const origin = `ws://localhost:${(httpServer.address() as AddressInfo).port}`;
51+
await expect.poll(() => context?.serviceWorkers()).toHaveLength(1);
52+
await context.pages()[0].goto(new URL('/popup.html', context.serviceWorkers()[0].url()).toString());
53+
await context.pages()[0].getByRole('textbox', { name: 'Bridge Server URL:' }).clear();
54+
await context.pages()[0].getByRole('textbox', { name: 'Bridge Server URL:' }).fill(`${origin}${server.EXTENSION_PATH}`);
55+
await context.pages()[0].getByRole('button', { name: 'Share This Tab' }).click();
56+
await context.pages()[0].goto('about:blank');
57+
const browser = await playwright.chromium.connectOverCDP(`${origin}${server.CDP_PATH}`);
58+
await use(browser);
59+
httpServer.close();
60+
}, { scope: 'worker' }],
61+
62+
context: async ({ browser }, use) => {
63+
await use(browser.contexts()[0]);
64+
},
65+
66+
page: async ({ browser }, use) => {
67+
await use(browser.contexts()[0].pages()[0]);
68+
}
69+
});

tests/extension/playwright.config.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { config as loadEnv } from 'dotenv';
18+
loadEnv({ path: path.join(__dirname, '..', '..', '.env') });
19+
process.env.PWTEST_UNDER_TEST = '1';
20+
21+
import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@playwright/test';
22+
import * as path from 'path';
23+
24+
process.env.PWPAGE_IMPL = 'extension';
25+
26+
const outputDir = path.join(__dirname, '..', '..', 'test-results');
27+
const testDir = path.join(__dirname, '..');
28+
const config: Config<PlaywrightWorkerOptions & PlaywrightTestOptions> = {
29+
testDir,
30+
outputDir,
31+
timeout: 30000,
32+
globalTimeout: 5400000,
33+
workers: process.env.CI ? 1 : undefined,
34+
forbidOnly: !!process.env.CI,
35+
retries: process.env.CI ? 3 : 0,
36+
reporter: process.env.CI ? [
37+
['dot'],
38+
['json', { outputFile: path.join(outputDir, 'report.json') }],
39+
['blob', { fileName: `${process.env.PWTEST_BOT_NAME}.zip` }],
40+
] : 'line',
41+
projects: [],
42+
};
43+
44+
const metadata = {
45+
platform: process.platform,
46+
headless: true,
47+
browserName: 'extension',
48+
channel: undefined,
49+
mode: 'default',
50+
video: false,
51+
};
52+
53+
config.projects.push({
54+
name: 'extension',
55+
// Share screenshots with chromium.
56+
snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-chromium{ext}',
57+
use: {
58+
browserName: 'chromium',
59+
},
60+
testDir: path.join(testDir, 'page'),
61+
metadata,
62+
});
63+
64+
export default config;

tests/page/pageTest.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { androidTest } from '../android/androidTest';
2121
import { browserTest } from '../config/browserTest';
2222
import { electronTest } from '../electron/electronTest';
2323
import { webView2Test } from '../webview2/webView2Test';
24+
import { extensionTest } from '../extension/extensionTest';
2425
import type { PageTestFixtures, PageWorkerFixtures } from './pageTestApi';
2526
import type { ServerFixtures, ServerWorkerOptions } from '../config/serverFixtures';
2627
import { expect as baseExpect } from '@playwright/test';
@@ -34,6 +35,8 @@ if (process.env.PWPAGE_IMPL === 'electron')
3435
impl = electronTest;
3536
if (process.env.PWPAGE_IMPL === 'webview2')
3637
impl = webView2Test;
38+
if (process.env.PWPAGE_IMPL === 'extension')
39+
impl = extensionTest;
3740

3841
export const test = impl;
3942

0 commit comments

Comments
 (0)