Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .claude/skills/roll-playwright.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
name: roll-playwright
description: Roll @playwright/test to the latest next version, build, test, and push a PR branch
user_invocable: true
---

# Roll Playwright Dependency

Follow these steps in order. Stop and report to the user if any step fails.

## 1. Get the latest version

Run `npm info @playwright/test@next version` to get the latest available next version. Save this version string for later.

## 2. Update package.json

Update the `@playwright/test` version in `devDependencies` in `package.json` to the version from step 1.

## 3. Install dependencies

Run `npm i` to update `package-lock.json`.

## 4. Copy reused code

Run `node ./utils/roll-locally`.

## 5. Build

Run `npm run build`.
If this fails, attempt best effort at fixing.

## 6. Test

Run `npm run test -- --project=default`.
If this fails, attempt best effort at fixing.

## 7. Create branch, commit, and push

- Create a new branch named `roll-pwt-<version>` (e.g. `roll-pwt-1.58.2-beta-1770322573000`)
- Stage `package.json` and `package-lock.json`
- Commit with message: `chore: roll playwright to <version>`
- Do NOT add Co-Authored-By to the commit message
- Push the branch to origin
24 changes: 12 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
},
"devDependencies": {
"@babel/preset-typescript": "^7.23.2",
"@playwright/test": "1.58.2-beta-1770322573000",
"@playwright/test": "1.59.0-alpha-1773706743000",
"@types/babel__core": "^7.20.3",
"@types/babel__helper-plugin-utils": "^7.10.2",
"@types/babel__traverse": "^7.20.3",
Expand Down
4 changes: 2 additions & 2 deletions src/playwrightTestServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export class PlaywrightTestServer {
return;

// Locations are regular expressions.
const locationPatterns = locations ? locations.map(escapeRegex) : undefined;
const locationPatterns = locations ? locations.map(escapeRegex) : [];
const options: Parameters<TestServerInterface['runTests']>['0'] = {
projects: this._model.enabledProjectsFilter(),
locations: locationPatterns,
Expand Down Expand Up @@ -344,7 +344,7 @@ export class PlaywrightTestServer {
}

// Locations are regular expressions.
const locationPatterns = locations ? locations.map(escapeRegex) : undefined;
const locationPatterns = locations ? locations.map(escapeRegex) : [];
const options: Parameters<TestServerInterface['runTests']>['0'] = {
projects: this._model.enabledProjectsFilter(),
locations: locationPatterns,
Expand Down
2 changes: 1 addition & 1 deletion src/testModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ export class TestModel extends DisposableBase {
if (representsPath)
hasPathItem = true;
else
testIds.push(...collectTestIds(treeItem));
testIds.push(...collectTestIds(treeItem).testIds);
}

// known bug: for a combination of location items, and test IDs outside those locations, those test IDs will never be run.
Expand Down
2 changes: 1 addition & 1 deletion src/testTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export class TestTree extends DisposableBase {
}
}

const upstreamTree = new upstream.TestTree(workspaceFSPath, rootSuite, [], undefined, path.sep);
const upstreamTree = new upstream.TestTree(workspaceFSPath, rootSuite, [], undefined, path.sep, false);
upstreamTree.sortAndPropagateStatus();
upstreamTree.flattenForSingleProject();

Expand Down
3 changes: 2 additions & 1 deletion src/upstream/testServerInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,14 @@ export interface TestServerInterface {
locations?: string[];
grep?: string;
grepInvert?: string;
onlyChanged?: string;
}): Promise<{
report: ReportEntry[],
status: reporterTypes.FullResult['status']
}>;

runTests(params: {
locations?: string[];
locations: string[];
grep?: string;
grepInvert?: string;
testIds?: string[];
Expand Down
80 changes: 53 additions & 27 deletions src/upstream/testTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class TestTree {
private _treeItemByTestId = new Map<string, TestItem | TestCaseItem>();
readonly pathSeparator: string;

constructor(rootFolder: string, rootSuite: reporterTypes.Suite | undefined, loadErrors: reporterTypes.TestError[], projectFilters: Map<string, boolean> | undefined, pathSeparator: string) {
constructor(rootFolder: string, rootSuite: reporterTypes.Suite | undefined, loadErrors: reporterTypes.TestError[], projectFilters: Map<string, boolean> | undefined, pathSeparator: string, hideFiles: boolean) {
const filterProjects = projectFilters && [...projectFilters.values()].some(Boolean);
this.pathSeparator = pathSeparator;
this.rootItem = {
Expand All @@ -81,11 +81,11 @@ export class TestTree {
};
this._treeItemById.set(rootFolder, this.rootItem);

const visitSuite = (project: reporterTypes.FullProject, parentSuite: reporterTypes.Suite, parentGroup: GroupItem) => {
for (const suite of parentSuite.suites) {
const visitSuite = (project: reporterTypes.FullProject, parentSuite: reporterTypes.Suite, parentGroup: GroupItem, mode: 'tests' | 'suites' | 'all') => {
for (const suite of mode === 'tests' ? [] : parentSuite.suites) {
if (!suite.title) {
// Flatten anonymous describes.
visitSuite(project, suite, parentGroup);
visitSuite(project, suite, parentGroup, 'all');
continue;
}

Expand All @@ -105,10 +105,10 @@ export class TestTree {
};
this._addChild(parentGroup, group);
}
visitSuite(project, suite, group);
visitSuite(project, suite, group, 'all');
}

for (const test of parentSuite.tests) {
for (const test of mode === 'suites' ? [] : parentSuite.tests) {
const title = test.title;
let testCaseItem = parentGroup.children.find(t => t.kind !== 'group' && t.title === title) as TestCaseItem;
if (!testCaseItem) {
Expand Down Expand Up @@ -167,8 +167,16 @@ export class TestTree {
if (filterProjects && !projectFilters.get(projectSuite.title))
continue;
for (const fileSuite of projectSuite.suites) {
const fileItem = this._fileItem(fileSuite.location!.file.split(pathSeparator), true);
visitSuite(projectSuite.project()!, fileSuite, fileItem);
if (hideFiles) {
visitSuite(projectSuite.project()!, fileSuite, this.rootItem, 'suites');
if (fileSuite.tests.length) {
const defaultDescribeItem = this._defaultDescribeItem();
visitSuite(projectSuite.project()!, fileSuite, defaultDescribeItem, 'tests');
}
} else {
const fileItem = this._fileItem(fileSuite.location!.file.split(pathSeparator), true);
visitSuite(projectSuite.project()!, fileSuite, fileItem, 'all');
}
}
}

Expand Down Expand Up @@ -242,6 +250,26 @@ export class TestTree {
return fileItem;
}

private _defaultDescribeItem(): GroupItem {
let defaultDescribeItem = this._treeItemById.get('<anonymous>') as GroupItem;
if (!defaultDescribeItem) {
defaultDescribeItem = {
kind: 'group',
subKind: 'describe',
id: '<anonymous>',
title: '<anonymous>',
location: { file: '', line: 0, column: 0 },
duration: 0,
parent: this.rootItem,
children: [],
status: 'none',
hasLoadErrors: false,
};
this._addChild(this.rootItem, defaultDescribeItem);
}
return defaultDescribeItem;
}

sortAndPropagateStatus() {
sortAndPropagateStatus(this.rootItem);
}
Expand All @@ -268,17 +296,6 @@ export class TestTree {
this.rootItem = shortRoot;
}

testIds(): Set<string> {
const result = new Set<string>();
const visit = (treeItem: TreeItem) => {
if (treeItem.kind === 'case')
treeItem.tests.forEach(t => result.add(t.id));
treeItem.children.forEach(visit);
};
visit(this.rootItem);
return result;
}

fileNames(): string[] {
const result = new Set<string>();
const visit = (treeItem: TreeItem) => {
Expand All @@ -305,8 +322,8 @@ export class TestTree {
return this._treeItemById.get(id);
}

collectTestIds(treeItem?: TreeItem): Set<string> {
return treeItem ? collectTestIds(treeItem) : new Set();
collectTestIds(treeItem: TreeItem) {
return collectTestIds(treeItem);
}
}

Expand Down Expand Up @@ -347,18 +364,27 @@ export function sortAndPropagateStatus(treeItem: TreeItem) {
treeItem.status = 'passed';
}

export function collectTestIds(treeItem: TreeItem): Set<string> {
export function collectTestIds(treeItem: TreeItem): { testIds: Set<string>, locations: Set<string> } {
const testIds = new Set<string>();
const locations = new Set<string>();
const visit = (treeItem: TreeItem) => {
if (treeItem.kind !== 'test' && treeItem.kind !== 'case') {
treeItem.children.forEach(visit);
return;
}

let fileItem: TreeItem = treeItem;
while (fileItem && fileItem.parent && !(fileItem.kind === 'group' && fileItem.subKind === 'file'))
fileItem = fileItem.parent;
locations.add(fileItem.location.file);

if (treeItem.kind === 'case')
treeItem.tests.map(t => t.id).forEach(id => testIds.add(id));
else if (treeItem.kind === 'test')
testIds.add(treeItem.id);
treeItem.tests.forEach(test => testIds.add(test.id));
else
treeItem.children?.forEach(visit);
testIds.add(treeItem.id);
};
visit(treeItem);
return testIds;
return { testIds, locations };
}

export const statusEx = Symbol('statusEx');
2 changes: 1 addition & 1 deletion tests-integration/tests/baseTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const test = base.extend<TestFixtures>({
if (packageManager === 'pnpm-pnp')
await fs.promises.writeFile(path.join(projectPath, '.npmrc'), 'node-linker=pnp');

spawnSync(`${command} --quiet --browser=chromium --gha --install-deps`, {
spawnSync(`${command} --quiet --browser=chromium --gha${process.env.CI ? ' --install-deps' : ''}`, {
cwd: projectPath,
stdio: 'inherit',
shell: true,
Expand Down
5 changes: 4 additions & 1 deletion tests/mock/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,10 @@ export class TestRun {
result.push(' Output:');
result.push(...this._renderOutput());
}
return trimLog(result.join(`\n${indent}`)) + `\n${indent}`;
let log = trimLog(result.join(`\n${indent}`)) + `\n${indent}`;
// Strip Playwright's "Context for AI" details block as it contains absolute paths.
log = log.replace(/\n?\s*<br><br><details><summary>Context for AI<\/summary>[\s\S]*?<\/details>/g, '');
return log;
}

renderOutput(): string {
Expand Down
3 changes: 2 additions & 1 deletion tests/run-tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,8 @@ test('should provide page snapshot to copilot', async ({ activate }) => {
});

const testRun = await testController.run();
const log = testRun.renderLog({ messages: true });
const allMessages = [...testRun.entries.values()].flat().flatMap(e => e.messages ?? []);
const log = allMessages.map(m => m.message.render()).join('\n');
expect(log).toContain(`<details><summary>Context for AI</summary>`);
expect(log).toContain(`# Page snapshot`);
expect(log).toContain(`- button "click me"`);
Expand Down
Loading