Skip to content

Commit d690e3d

Browse files
szuendDevtools-frontend LUCI CQ
authored andcommitted
Add script to let gemini migrate an e2e test in a fresh worktree
This is a general pattern we probably want to save for the future: Use gclient-new-workdir.py to create lightweight worktrees, that share the .git objects but allow for building, linting and uploading CLs. Then gemini or some coding agent can work on it and afterwards we can nuke the whole worktree if it went off the rails. Since .git is shared the branch survives in the main checkout anyway. [email protected] Bug: 416394514 Change-Id: I61dfb18c7e41becba294871687c8995e8d0d520c Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6712996 Reviewed-by: Alex Rudenko <[email protected]> Commit-Queue: Simon Zünd <[email protected]>
1 parent 740367c commit d690e3d

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright 2025 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import {execSync, spawn} from 'child_process';
6+
7+
const args = process.argv.slice(2);
8+
if (args.length !== 2) {
9+
throw new Error('Usage: node scripts/migration/e2e_non_hosted_gemini.ts <path> <issue number>');
10+
}
11+
12+
const oldTestFilePath = args[0];
13+
const newTestFilePath = oldTestFilePath.replace('/e2e/', '/e2e_non_hosted/');
14+
const newTestDirectory = newTestFilePath.substring(0, newTestFilePath.lastIndexOf('/'));
15+
const issueNumber = args[1];
16+
const branch = `fix-${issueNumber}`;
17+
const worktreeBasePath = `/tmp/worktree-${branch}`;
18+
const worktreePath = `${worktreeBasePath}/devtools-frontend`;
19+
20+
const prompt = `Migrate @${oldTestFilePath} to @${newTestFilePath}. ISSUENUMBER is ${issueNumber}.
21+
22+
Step by step instructions:
23+
24+
1) Read all tests in @${newTestDirectory} as examples to understand the aspects of the already migrated files.
25+
2) Read all files in @test/e2e_non_hosted/shared.
26+
3) Read the non-migrated test file @${oldTestFilePath}.
27+
4) Read relevant helpers files imported from the test file.
28+
5) Create an empty file for the migrated test @${newTestFilePath}.
29+
6) Update BUILD.gn files to include the new file into the build and remove it from the BUILD.gn files in the e2e folder.
30+
7) Migrate the test file following the following instructions:
31+
32+
- each test function should receive a 'devToolsPage' via its arguments and 'inspectedPage' if needed.
33+
- when calling helpers pass the 'devToolsPage' as the last argument.
34+
- update the helpers that are used by the test to accept 'devToolsPage' as the last argument if needed. CRITICAL: DO NOT UPDATE helpers if an equivalent already exists on the 'DevToolsPage'. Remember that $$, $, waitFor, click, hover, typeText helpers are available directly on devToolsPage. Rewrite accordingly.
35+
- keep the helpers from @test/e2e/helpers in place.
36+
- in helpers, the 'devToolsPage' argument should be the last one and it should be optional defaulting to 'getBrowserAndPagesWrappers().devToolsPage'.
37+
- use the globally available 'setup' function define experiments and flags for the test.
38+
- rewrite any beforeEach/afterEach hooks to be local helper functions called from the test directly.
39+
- instead of using 'devToolsPage.keyboard.press' use 'devToolsPage.keyboard.pressKey'.
40+
- instead of using global 'click' use 'devToolsPage.click'.
41+
42+
8) After you are done with changes, run 'npm run test -- -t StrictTypes ${newTestFilePath}' to verify.
43+
9) Delete the original test file.
44+
10) Run 'git cl presubmit --upload --force'
45+
`;
46+
47+
function runProcess(command, args, options) {
48+
return new Promise((resolve, reject) => {
49+
const proc = spawn(command, args, options);
50+
51+
// Rejects on spawn errors (e.g., command not found)
52+
proc.on('error', err => {
53+
reject(err);
54+
});
55+
56+
// Resolves or rejects based on the final exit code
57+
proc.on('close', code => {
58+
if (code === 0) {
59+
console.log('Child process completed successfully.');
60+
resolve(code);
61+
} else {
62+
// Rejects if the script fails (non-zero exit code)
63+
const err = new Error(`Child process failed with exit code: ${code}`);
64+
reject(err);
65+
}
66+
});
67+
});
68+
}
69+
70+
try {
71+
execSync(`git branch -D ${branch} || true`);
72+
execSync(`gclient-new-workdir.py .. ${worktreeBasePath}`);
73+
74+
execSync(`git new-branch ${branch}`, {cwd: worktreePath});
75+
execSync('gclient sync -j20 -Df', {cwd: worktreePath});
76+
execSync('gn gen out/StrictTypes --args="is_debug=true"', {cwd: worktreePath});
77+
78+
await runProcess(
79+
'npx',
80+
['@google/gemini-cli', '-y', '-p', prompt],
81+
{
82+
stdio: 'inherit',
83+
cwd: worktreePath,
84+
env: process.env,
85+
}
86+
);
87+
88+
console.log('Migration successful. Committing changes...');
89+
execSync(`git add . && git commit -a -m "Migrate ${oldTestFilePath}"`, {cwd: worktreePath});
90+
execSync(`git cl upload -a -s -d -f -x ${issueNumber}`, {cwd: worktreePath});
91+
92+
} catch (error) {
93+
console.error('Migration failed:', error.message);
94+
process.exit(1);
95+
96+
} finally {
97+
console.log('Cleaning up worktree...');
98+
execSync(`rm -rf ${worktreeBasePath}`);
99+
}

0 commit comments

Comments
 (0)