Skip to content

Commit 795a9d5

Browse files
authored
chore: generalize status & action as code (microsoft#188)
1 parent 4a19e18 commit 795a9d5

12 files changed

+187
-88
lines changed

src/context.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ type PageOrFrameLocator = playwright.Page | playwright.FrameLocator;
3333
type RunOptions = {
3434
captureSnapshot?: boolean;
3535
waitForCompletion?: boolean;
36-
status?: string;
3736
noClearFileChooser?: boolean;
3837
};
3938

@@ -79,8 +78,8 @@ export class Context {
7978

8079
async listTabs(): Promise<string> {
8180
if (!this._tabs.length)
82-
return 'No tabs open';
83-
const lines: string[] = ['Open tabs:'];
81+
return '### No tabs open';
82+
const lines: string[] = ['### Open tabs'];
8483
for (let i = 0; i < this._tabs.length; i++) {
8584
const tab = this._tabs[i];
8685
const title = await tab.page.title();
@@ -172,6 +171,10 @@ export class Context {
172171
}
173172
}
174173

174+
type RunResult = {
175+
code: string[];
176+
};
177+
175178
class Tab {
176179
readonly context: Context;
177180
readonly page: playwright.Page;
@@ -207,33 +210,33 @@ class Tab {
207210
await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => {});
208211
}
209212

210-
async run(callback: (tab: Tab) => Promise<void | string>, options?: RunOptions): Promise<ToolResult> {
211-
let actionCode: string | undefined;
213+
async run(callback: (tab: Tab) => Promise<RunResult>, options?: RunOptions): Promise<ToolResult> {
214+
let runResult: RunResult | undefined;
212215
try {
213216
if (!options?.noClearFileChooser)
214217
this._fileChooser = undefined;
215218
if (options?.waitForCompletion)
216-
actionCode = await waitForCompletion(this.page, () => callback(this)) ?? undefined;
219+
runResult = await waitForCompletion(this.page, () => callback(this)) ?? undefined;
217220
else
218-
actionCode = await callback(this) ?? undefined;
221+
runResult = await callback(this) ?? undefined;
219222
} finally {
220223
if (options?.captureSnapshot)
221224
this._snapshot = await PageSnapshot.create(this.page);
222225
}
223226

224227
const result: string[] = [];
225-
if (options?.status)
226-
result.push(options.status, '');
228+
result.push(`- Ran code:
229+
\`\`\`js
230+
${runResult.code.join('\n')}
231+
\`\`\`
232+
`);
227233

228234
if (this.context.tabs().length > 1)
229235
result.push(await this.context.listTabs(), '');
230236

231-
if (actionCode)
232-
result.push('- Action: ' + actionCode, '');
233-
234237
if (this._snapshot) {
235238
if (this.context.tabs().length > 1)
236-
result.push('Current tab:');
239+
result.push('### Current tab');
237240
result.push(this._snapshot.text({ hasFileChooser: !!this._fileChooser }));
238241
}
239242

@@ -245,14 +248,14 @@ class Tab {
245248
};
246249
}
247250

248-
async runAndWait(callback: (tab: Tab) => Promise<void | string>, options?: RunOptions): Promise<ToolResult> {
251+
async runAndWait(callback: (tab: Tab) => Promise<RunResult>, options?: RunOptions): Promise<ToolResult> {
249252
return await this.run(callback, {
250253
waitForCompletion: true,
251254
...options,
252255
});
253256
}
254257

255-
async runAndWaitWithSnapshot(callback: (snapshot: PageSnapshot) => Promise<void | string>, options?: RunOptions): Promise<ToolResult> {
258+
async runAndWaitWithSnapshot(callback: (snapshot: PageSnapshot) => Promise<RunResult>, options?: RunOptions): Promise<ToolResult> {
256259
return await this.run(tab => callback(tab.lastSnapshot()), {
257260
captureSnapshot: true,
258261
waitForCompletion: true,

src/tools/common.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,16 @@ const resize: ToolFactory = captureSnapshot => ({
7878
const validatedParams = resizeSchema.parse(params);
7979

8080
const tab = context.currentTab();
81-
return await tab.run(
82-
tab => tab.page.setViewportSize({ width: validatedParams.width, height: validatedParams.height }),
83-
{
84-
status: `Resized browser window`,
85-
captureSnapshot,
86-
}
87-
);
81+
return await tab.run(async tab => {
82+
await tab.page.setViewportSize({ width: validatedParams.width, height: validatedParams.height });
83+
const code = [
84+
`// Resize browser window to ${validatedParams.width}x${validatedParams.height}`,
85+
`await page.setViewportSize({ width: ${validatedParams.width}, height: ${validatedParams.height} });`
86+
];
87+
return { code };
88+
}, {
89+
captureSnapshot,
90+
});
8891
},
8992
});
9093

src/tools/files.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@ const uploadFile: ToolFactory = captureSnapshot => ({
3535
const tab = context.currentTab();
3636
return await tab.runAndWait(async () => {
3737
await tab.submitFileChooser(validatedParams.paths);
38+
const code = [
39+
`// <internal code to chose files ${validatedParams.paths.join(', ')}`,
40+
];
41+
return { code };
3842
}, {
39-
status: `Chose files ${validatedParams.paths.join(', ')}`,
4043
captureSnapshot,
4144
noClearFileChooser: true,
4245
});

src/tools/keyboard.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ const pressKey: ToolFactory = captureSnapshot => ({
3434
const validatedParams = pressKeySchema.parse(params);
3535
return await context.currentTab().runAndWait(async tab => {
3636
await tab.page.keyboard.press(validatedParams.key);
37-
return `await page.keyboard.press('${validatedParams.key}');`;
37+
const code = [
38+
`// Press ${validatedParams.key}`,
39+
`await page.keyboard.press('${validatedParams.key}');`,
40+
];
41+
return { code };
3842
}, {
39-
status: `Pressed key ${validatedParams.key}`,
4043
captureSnapshot,
4144
});
4245
},

src/tools/navigate.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@ const navigate: ToolFactory = captureSnapshot => ({
3535
const currentTab = await context.ensureTab();
3636
return await currentTab.run(async tab => {
3737
await tab.navigate(validatedParams.url);
38-
return `await page.goto('${validatedParams.url}');`;
38+
const code = [
39+
`// Navigate to ${validatedParams.url}`,
40+
`await page.goto('${validatedParams.url}');`,
41+
];
42+
return { code };
3943
}, {
40-
status: `Navigated to ${validatedParams.url}`,
4144
captureSnapshot,
4245
});
4346
},
@@ -55,9 +58,12 @@ const goBack: ToolFactory = snapshot => ({
5558
handle: async context => {
5659
return await context.currentTab().runAndWait(async tab => {
5760
await tab.page.goBack();
58-
return `await page.goBack();`;
61+
const code = [
62+
`// Navigate back`,
63+
`await page.goBack();`,
64+
];
65+
return { code };
5966
}, {
60-
status: 'Navigated back',
6167
captureSnapshot: snapshot,
6268
});
6369
},
@@ -75,9 +81,12 @@ const goForward: ToolFactory = snapshot => ({
7581
handle: async context => {
7682
return await context.currentTab().runAndWait(async tab => {
7783
await tab.page.goForward();
78-
return `await page.goForward();`;
84+
const code = [
85+
`// Navigate forward`,
86+
`await page.goForward();`,
87+
];
88+
return { code };
7989
}, {
80-
status: 'Navigated forward',
8190
captureSnapshot: snapshot,
8291
});
8392
},

src/tools/screen.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,16 @@ const click: Tool = {
7979
handle: async (context, params) => {
8080
return await context.currentTab().runAndWait(async tab => {
8181
const validatedParams = clickSchema.parse(params);
82+
const code = [
83+
`// Click mouse at coordinates (${validatedParams.x}, ${validatedParams.y})`,
84+
`await page.mouse.move(${validatedParams.x}, ${validatedParams.y});`,
85+
`await page.mouse.down();`,
86+
`await page.mouse.up();`,
87+
];
8288
await tab.page.mouse.move(validatedParams.x, validatedParams.y);
8389
await tab.page.mouse.down();
8490
await tab.page.mouse.up();
85-
}, {
86-
status: 'Clicked mouse',
91+
return { code };
8792
});
8893
},
8994
};
@@ -110,8 +115,14 @@ const drag: Tool = {
110115
await tab.page.mouse.down();
111116
await tab.page.mouse.move(validatedParams.endX, validatedParams.endY);
112117
await tab.page.mouse.up();
113-
}, {
114-
status: `Dragged mouse from (${validatedParams.startX}, ${validatedParams.startY}) to (${validatedParams.endX}, ${validatedParams.endY})`,
118+
const code = [
119+
`// Drag mouse from (${validatedParams.startX}, ${validatedParams.startY}) to (${validatedParams.endX}, ${validatedParams.endY})`,
120+
`await page.mouse.move(${validatedParams.startX}, ${validatedParams.startY});`,
121+
`await page.mouse.down();`,
122+
`await page.mouse.move(${validatedParams.endX}, ${validatedParams.endY});`,
123+
`await page.mouse.up();`,
124+
];
125+
return { code };
115126
});
116127
},
117128
};
@@ -132,11 +143,17 @@ const type: Tool = {
132143
handle: async (context, params) => {
133144
const validatedParams = typeSchema.parse(params);
134145
return await context.currentTab().runAndWait(async tab => {
146+
const code = [
147+
`// Type ${validatedParams.text}`,
148+
`await page.keyboard.type('${validatedParams.text}');`,
149+
];
135150
await tab.page.keyboard.type(validatedParams.text);
136-
if (validatedParams.submit)
151+
if (validatedParams.submit) {
152+
code.push(`// Submit text`);
153+
code.push(`await page.keyboard.press('Enter');`);
137154
await tab.page.keyboard.press('Enter');
138-
}, {
139-
status: `Typed text "${validatedParams.text}"`,
155+
}
156+
return { code };
140157
});
141158
},
142159
};

src/tools/snapshot.ts

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ const snapshot: Tool = {
3232

3333
handle: async context => {
3434
const tab = await context.ensureTab();
35-
return await tab.run(async () => {}, { captureSnapshot: true });
35+
return await tab.run(async () => {
36+
const code = [`// <internal code to capture accessibility snapshot>`];
37+
return { code };
38+
}, { captureSnapshot: true });
3639
},
3740
};
3841

@@ -53,11 +56,12 @@ const click: Tool = {
5356
const validatedParams = elementSchema.parse(params);
5457
return await context.currentTab().runAndWaitWithSnapshot(async snapshot => {
5558
const locator = snapshot.refLocator(validatedParams.ref);
56-
const action = `await page.${await generateLocator(locator)}.click();`;
59+
const code = [
60+
`// Click ${validatedParams.element}`,
61+
`await page.${await generateLocator(locator)}.click();`
62+
];
5763
await locator.click();
58-
return action;
59-
}, {
60-
status: `Clicked "${validatedParams.element}"`,
64+
return { code };
6165
});
6266
},
6367
};
@@ -82,11 +86,12 @@ const drag: Tool = {
8286
return await context.currentTab().runAndWaitWithSnapshot(async snapshot => {
8387
const startLocator = snapshot.refLocator(validatedParams.startRef);
8488
const endLocator = snapshot.refLocator(validatedParams.endRef);
85-
const action = `await page.${await generateLocator(startLocator)}.dragTo(page.${await generateLocator(endLocator)});`;
89+
const code = [
90+
`// Drag ${validatedParams.startElement} to ${validatedParams.endElement}`,
91+
`await page.${await generateLocator(startLocator)}.dragTo(page.${await generateLocator(endLocator)});`
92+
];
8693
await startLocator.dragTo(endLocator);
87-
return action;
88-
}, {
89-
status: `Dragged "${validatedParams.startElement}" to "${validatedParams.endElement}"`,
94+
return { code };
9095
});
9196
},
9297
};
@@ -103,11 +108,12 @@ const hover: Tool = {
103108
const validatedParams = elementSchema.parse(params);
104109
return await context.currentTab().runAndWaitWithSnapshot(async snapshot => {
105110
const locator = snapshot.refLocator(validatedParams.ref);
106-
const action = `await page.${await generateLocator(locator)}.hover();`;
111+
const code = [
112+
`// Hover over ${validatedParams.element}`,
113+
`await page.${await generateLocator(locator)}.hover();`
114+
];
107115
await locator.hover();
108-
return action;
109-
}, {
110-
status: `Hovered over "${validatedParams.element}"`,
116+
return { code };
111117
});
112118
},
113119
};
@@ -131,21 +137,22 @@ const type: Tool = {
131137
return await context.currentTab().runAndWaitWithSnapshot(async snapshot => {
132138
const locator = snapshot.refLocator(validatedParams.ref);
133139

134-
let action = '';
140+
const code: string[] = [];
135141
if (validatedParams.slowly) {
136-
action = `await page.${await generateLocator(locator)}.pressSequentially(${javascript.quote(validatedParams.text)});`;
142+
code.push(`// Press "${validatedParams.text}" sequentially into "${validatedParams.element}"`);
143+
code.push(`await page.${await generateLocator(locator)}.pressSequentially(${javascript.quote(validatedParams.text)});`);
137144
await locator.pressSequentially(validatedParams.text);
138145
} else {
139-
action = `await page.${await generateLocator(locator)}.fill(${javascript.quote(validatedParams.text)});`;
146+
code.push(`// Fill "${validatedParams.text}" into "${validatedParams.element}"`);
147+
code.push(`await page.${await generateLocator(locator)}.fill(${javascript.quote(validatedParams.text)});`);
140148
await locator.fill(validatedParams.text);
141149
}
142150
if (validatedParams.submit) {
143-
action += `\nawait page.${await generateLocator(locator)}.press('Enter');`;
151+
code.push(`// Submit text`);
152+
code.push(`await page.${await generateLocator(locator)}.press('Enter');`);
144153
await locator.press('Enter');
145154
}
146-
return action;
147-
}, {
148-
status: `Typed "${validatedParams.text}" into "${validatedParams.element}"`,
155+
return { code };
149156
});
150157
},
151158
};
@@ -166,11 +173,12 @@ const selectOption: Tool = {
166173
const validatedParams = selectOptionSchema.parse(params);
167174
return await context.currentTab().runAndWaitWithSnapshot(async snapshot => {
168175
const locator = snapshot.refLocator(validatedParams.ref);
169-
const action = `await page.${await generateLocator(locator)}.selectOption(${javascript.formatObject(validatedParams.values)});`;
176+
const code = [
177+
`// Select options [${validatedParams.values.join(', ')}] in ${validatedParams.element}`,
178+
`await page.${await generateLocator(locator)}.selectOption(${javascript.formatObject(validatedParams.values)});`
179+
];
170180
await locator.selectOption(validatedParams.values);
171-
return action;
172-
}, {
173-
status: `Selected option in "${validatedParams.element}"`,
181+
return { code };
174182
});
175183
},
176184
};

src/tools/tabs.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ const selectTab: ToolFactory = captureSnapshot => ({
5151
const validatedParams = selectTabSchema.parse(params);
5252
await context.selectTab(validatedParams.index);
5353
const currentTab = await context.ensureTab();
54-
return await currentTab.run(async () => {}, { captureSnapshot });
54+
return await currentTab.run(async () => {
55+
const code = [
56+
`// <internal code to select tab ${validatedParams.index}>`,
57+
];
58+
return { code };
59+
}, { captureSnapshot });
5560
},
5661
});
5762

@@ -71,7 +76,12 @@ const newTab: Tool = {
7176
await context.newTab();
7277
if (validatedParams.url)
7378
await context.currentTab().navigate(validatedParams.url);
74-
return await context.currentTab().run(async () => {}, { captureSnapshot: true });
79+
return await context.currentTab().run(async () => {
80+
const code = [
81+
`// <internal code to open a new tab>`,
82+
];
83+
return { code };
84+
}, { captureSnapshot: true });
7585
},
7686
};
7787

@@ -90,8 +100,14 @@ const closeTab: ToolFactory = captureSnapshot => ({
90100
const validatedParams = closeTabSchema.parse(params);
91101
await context.closeTab(validatedParams.index);
92102
const currentTab = context.currentTab();
93-
if (currentTab)
94-
return await currentTab.run(async () => {}, { captureSnapshot });
103+
if (currentTab) {
104+
return await currentTab.run(async () => {
105+
const code = [
106+
`// <internal code to close tab ${validatedParams.index}>`,
107+
];
108+
return { code };
109+
}, { captureSnapshot });
110+
}
95111
return {
96112
content: [{
97113
type: 'text',

0 commit comments

Comments
 (0)