-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add rstest-cdp skill #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Summary of ChangesHello @fi3ework, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the debugging capabilities for AI agents by introducing the Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a new rstest-cdp skill, which is a powerful tool for debugging rstest tests using the Chrome DevTools Protocol. The implementation is well-structured, with a clear separation of concerns between plan parsing, CDP communication, session management, and sourcemap resolution. The use of instrumentation breakpoints to reliably set breakpoints before script execution is a clever and robust approach. The end-to-end tests cover various scenarios, ensuring the tool's reliability.
My feedback includes a few suggestions to improve maintainability and correctness. I've pointed out an opportunity to refactor duplicated code in the e2e tests, a small enhancement to improve the debuggability of the tool itself by adding more logging, and a correction in the skill's documentation to accurately reflect the output format.
Overall, this is an excellent contribution that adds a valuable debugging capability.
| it('evaluates locals via CDP', async () => { | ||
| const fixturesTargetPath = `${__dirname}/fixtures/basic`; | ||
|
|
||
| const profileSourcePath = path.join(fixturesTargetPath, 'src/profile.ts'); | ||
| const plan = { | ||
| runner: { | ||
| cmd: 'pnpm', | ||
| args: ['rstest', 'run', '-c', 'rstest.config.ts', '--include', 'test/profile.test.ts'], | ||
| cwd: fixturesTargetPath, | ||
| env: { | ||
| FORCE_COLOR: '0', | ||
| }, | ||
| }, | ||
| tasks: [ | ||
| { | ||
| description: 'Inspect formatted profile fields', | ||
| sourcePath: profileSourcePath, | ||
| line: 7, | ||
| column: 0, | ||
| expressions: ['trimmed', 'normalized', 'displayName', 'role'], | ||
| }, | ||
| ], | ||
| }; | ||
| const stdinPayload = JSON.stringify(plan, null, 2); | ||
|
|
||
| let stdout = ''; | ||
| const cli = x( | ||
| 'node', | ||
| [ | ||
| path.resolve(__dirname, '../../packages/rstest-cdp/dist/rstest-cdp.cjs'), | ||
| '--plan', | ||
| '-', | ||
| '--debug', | ||
| '1', | ||
| ], | ||
| { | ||
| nodeOptions: { | ||
| cwd: fixturesTargetPath, | ||
| }, | ||
| }, | ||
| ); | ||
|
|
||
| cli.process?.stdout?.on('data', (chunk) => { | ||
| stdout += chunk.toString(); | ||
| }); | ||
|
|
||
| cli.process?.stdin?.write(stdinPayload); | ||
| cli.process?.stdin?.end(); | ||
|
|
||
| try { | ||
| await Promise.race([ | ||
| cli, | ||
| new Promise<void>((_resolve, reject) => { | ||
| setTimeout(() => reject(new Error('rstest-cdp timed out.')), 45_000); | ||
| }), | ||
| ]); | ||
| } catch (_error) { | ||
| const details = stdout.trim() ? `\n${stdout}` : ''; | ||
| throw new Error(`rstest-cdp failed.${details}`); | ||
| } | ||
|
|
||
| const result = JSON.parse(stdout) as DebugResult; | ||
| if (result.status === 'failed') { | ||
| throw new Error(`rstest-cdp failed.\n${JSON.stringify(result)}`); | ||
| } | ||
|
|
||
| expect(result.status).toBe('full_succeed'); | ||
| expect(result.meta).toBeTruthy(); | ||
| expect(result.meta?.pendingTaskIds.length).toBe(0); | ||
|
|
||
| expect(result.meta?.forwardedArgs).toContain('--pool.maxWorkers=1'); | ||
| expect(result.meta?.forwardedArgs).toContain('--pool.execArgv=--inspect-brk=0'); | ||
|
|
||
| const profileResult = result.results.find((item) => item.id === 'task-1'); | ||
| expect(profileResult).toBeTruthy(); | ||
| const values = Object.fromEntries( | ||
| (profileResult?.values || []).map((entry) => [entry.expression, entry.value]), | ||
| ); | ||
| expect(values.trimmed).toBe('Ada Lovelace'); | ||
| expect(values.normalized).toBe('ada-lovelace'); | ||
| expect(values.displayName).toBe('Ada Lovelace'); | ||
| expect(values.role).toBe('admin'); | ||
| }, 60_000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test case contains a lot of boilerplate for running the rstest-cdp CLI, which is already encapsulated in the runCdpDebug helper function. To improve maintainability and reduce code duplication, this test should be refactored to use the helper function, just like the other tests in this file.
it('evaluates locals via CDP', async () => {
const fixturesTargetPath = `${__dirname}/fixtures/basic`;
const profileSourcePath = path.join(fixturesTargetPath, 'src/profile.ts');
const plan = {
runner: {
cmd: 'pnpm',
args: ['rstest', 'run', '-c', 'rstest.config.ts', '--include', 'test/profile.test.ts'],
cwd: fixturesTargetPath,
env: {
FORCE_COLOR: '0',
},
},
tasks: [
{
description: 'Inspect formatted profile fields',
sourcePath: profileSourcePath,
line: 7,
column: 0,
expressions: ['trimmed', 'normalized', 'displayName', 'role'],
},
],
};
const result = await runCdpDebug({
plan,
cwd: fixturesTargetPath,
debug: true,
});
if (result.status === 'failed') {
throw new Error(`rstest-cdp failed.\n${JSON.stringify(result)}`);
}
expect(result.status).toBe('full_succeed');
expect(result.meta).toBeTruthy();
expect(result.meta?.pendingTaskIds.length).toBe(0);
expect(result.meta?.forwardedArgs).toContain('--pool.maxWorkers=1');
expect(result.meta?.forwardedArgs).toContain('--pool.execArgv=--inspect-brk=0');
const profileResult = result.results.find((item) => item.id === 'task-1');
expect(profileResult).toBeTruthy();
const values = Object.fromEntries(
(profileResult?.values || []).map((entry) => [entry.expression, entry.value]),
);
expect(values.trimmed).toBe('Ada Lovelace');
expect(values.normalized).toBe('ada-lovelace');
expect(values.displayName).toBe('Ada Lovelace');
expect(values.role).toBe('admin');
}, 60_000);| export const evaluateExpressions = async ({ | ||
| cdp, | ||
| callFrameId, | ||
| expressions, | ||
| }: { | ||
| cdp: CdpClient; | ||
| callFrameId: string; | ||
| expressions: string[]; | ||
| }): Promise<EvaluatedValue[]> => { | ||
| return Promise.all( | ||
| expressions.map(async (expression) => { | ||
| const result = await cdp.send<{ result?: RemoteObject }>('Debugger.evaluateOnCallFrame', { | ||
| callFrameId, | ||
| expression, | ||
| }); | ||
| const payload = result?.result; | ||
| let value = payload?.value; | ||
| const type = payload?.type; | ||
| const subtype = payload?.subtype; | ||
| const preview = payload?.description; | ||
|
|
||
| // For objects without a primitive value, fetch shallow properties | ||
| if (value === undefined && payload?.objectId) { | ||
| try { | ||
| const properties = await cdp.send<{ result?: PropertyDescriptor[] }>( | ||
| 'Runtime.getProperties', | ||
| { objectId: payload.objectId, ownProperties: true }, | ||
| ); | ||
| const shallow: Record<string, unknown> = {}; | ||
| for (const prop of properties?.result || []) { | ||
| if (!prop?.name || prop?.value == null) continue; | ||
| const propVal = prop.value; | ||
| shallow[prop.name] = propVal.value ?? propVal.description ?? propVal.type; | ||
| } | ||
| value = shallow; | ||
| } catch { | ||
| value = preview; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| expression, | ||
| value, | ||
| type, | ||
| subtype, | ||
| preview, | ||
| } satisfies EvaluatedValue; | ||
| }), | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The catch block for Runtime.getProperties is empty. While falling back to the preview description is a good defensive measure, swallowing the error silently can make it harder to debug issues with property fetching. Consider adding a debug log here to report the error when in debug mode. This will require passing the debugLog function into evaluateExpressions.
After applying this change, you'll also need to update the call site in packages/rstest-cdp/src/session.ts to pass this.debugLog.
export const evaluateExpressions = async ({
cdp,
callFrameId,
expressions,
debugLog,
}: {
cdp: CdpClient;
callFrameId: string;
expressions: string[];
debugLog?: (...args: unknown[]) => void;
}): Promise<EvaluatedValue[]> => {
return Promise.all(
expressions.map(async (expression) => {
const result = await cdp.send<{ result?: RemoteObject }>('Debugger.evaluateOnCallFrame', {
callFrameId,
expression,
});
const payload = result?.result;
let value = payload?.value;
const type = payload?.type;
const subtype = payload?.subtype;
const preview = payload?.description;
// For objects without a primitive value, fetch shallow properties
if (value === undefined && payload?.objectId) {
try {
const properties = await cdp.send<{ result?: PropertyDescriptor[] }>(
'Runtime.getProperties',
{ objectId: payload.objectId, ownProperties: true },
);
const shallow: Record<string, unknown> = {};
for (const prop of properties?.result || []) {
if (!prop?.name || prop?.value == null) continue;
const propVal = prop.value;
shallow[prop.name] = propVal.value ?? propVal.description ?? propVal.type;
}
value = shallow;
} catch (error) {
debugLog?.(
'Failed to get properties for objectId',
payload.objectId,
error instanceof Error ? error.message : String(error),
);
value = preview;
}
}
return {
expression,
value,
type,
subtype,
preview,
} satisfies EvaluatedValue;
}),
);
};
skills/rstest-cdp/SKILL.md
Outdated
|
|
||
| Read these fields: | ||
|
|
||
| - `ok`: success/failure |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation for the output format is incorrect. The output JSON does not contain an ok field. The field indicating the result status is status, which can have values 'full_succeed', 'partial_succeed', or 'failed'. Please update the documentation to reflect the correct field name and possible values.
| - `ok`: success/failure | |
| - `status`: 'full_succeed', 'partial_succeed', or 'failed' |
42112f4 to
d029773
Compare
human input:
introducing
rstest-cdppackage to let agent use skill bynpx rstest-cdp --plan -to set breakpoint at source file position (leverage sourcemap inference to dist file) and read the variable throw CDP while test is running. skip thelog -> build -> find log(sometimes agent even don't know the loop, lol) loop while testing.i tested the skill locally in Rsbuild codebase, by intentionally broke some source code and had opencode + sonnet 4.5 (using a relatively simple model to prevent it from directly locating the error in the source code) call skill to assist in troubleshooting, and it turned out to be useful. the usefulness of skill varies from project to project, and it will be more valuable in codebases with interleaved states or race conditions.
here's the exported transcript:
session-ses_3d89-sanitized.md
excerpt: