internal(browser): Add waitForTimeout to editor and debugger#1169
internal(browser): Add waitForTimeout to editor and debugger#1169
Conversation
| case 'number': | ||
| if (isNaN(value)) { | ||
| return identifier('NaN') | ||
| } |
There was a problem hiding this comment.
Without this, the NaN values would be rendered as nan for some reason (maybe prettier does a toLowerCase on number literals?)
| > | ||
| Wait for element | ||
| </DropdownMenu.Item> | ||
| <DropdownMenu.Item |
There was a problem hiding this comment.
Thoughtfully placed after "Wait for element" because it should be your default.
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: NaN timeout can be saved, breaking file reload
- Added NaN check in fromBrowserActionInstance to coerce NaN timeout values to 0 before serialization, preventing null in saved files.
Or push these changes by commenting:
@cursor push 5b7b05f8d2
Preview (5b7b05f8d2)
diff --git a/package-lock.json b/package-lock.json
--- a/package-lock.json
+++ b/package-lock.json
@@ -317,6 +317,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -625,6 +626,7 @@
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2",
@@ -1150,6 +1152,7 @@
"integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"chalk": "^4.1.1",
"fs-extra": "^9.0.1",
@@ -1592,6 +1595,7 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -2554,6 +2558,7 @@
"integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@inquirer/checkbox": "^3.0.1",
"@inquirer/confirm": "^4.0.1",
@@ -2891,6 +2896,7 @@
"integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0",
@@ -3201,6 +3207,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -3222,6 +3229,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz",
"integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">=14"
},
@@ -3258,6 +3266,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz",
"integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@opentelemetry/api-logs": "0.57.2",
"@types/shimmer": "^1.2.0",
@@ -3788,6 +3797,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.31.0.tgz",
"integrity": "sha512-cYJeP+6qN0UnBv1r09hXl0YorB8kXHv61BC0NUlBA8vxrylZ4/C8lnva3gd1E8n33DNYSaiGW+DuGoSt0QQ7Dw==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">=14"
}
@@ -6911,6 +6921,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz",
"integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -6987,6 +6998,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -6997,6 +7009,7 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -7094,6 +7107,7 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz",
"integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==",
"dev": true,
+ "peer": true,
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.16.1",
@@ -7962,6 +7976,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -9016,6 +9031,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -10869,16 +10885,6 @@
"node": ">= 0.8"
}
},
- "node_modules/encoding": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
- "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "iconv-lite": "^0.6.2"
- }
- },
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -11217,6 +11223,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -11355,6 +11362,7 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz",
"integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==",
"dev": true,
+ "peer": true,
"dependencies": {
"array-includes": "^3.1.7",
"array.prototype.findlastindex": "^1.2.3",
@@ -13068,6 +13076,7 @@
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
+ "peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
@@ -14988,6 +14997,7 @@
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"dompurify": "3.2.7",
"marked": "14.0.0"
@@ -16557,6 +16567,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -16581,6 +16592,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -16592,6 +16604,7 @@
"version": "7.53.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.0.tgz",
"integrity": "sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==",
+ "peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -17511,6 +17524,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -18678,6 +18692,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -18932,7 +18947,8 @@
"node_modules/tslib": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
- "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
+ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
+ "peer": true
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -19070,6 +19086,7 @@
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -19602,6 +19619,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -19712,6 +19730,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -20347,6 +20366,7 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
+ "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/src/views/BrowserTestEditor/actionAdapters.ts b/src/views/BrowserTestEditor/actionAdapters.ts
--- a/src/views/BrowserTestEditor/actionAdapters.ts
+++ b/src/views/BrowserTestEditor/actionAdapters.ts
@@ -42,5 +42,12 @@
}
}
+ if (action.method === 'page.waitForTimeout' && isNaN(action.timeout)) {
+ return {
+ ...action,
+ timeout: 0,
+ }
+ }
+
return action
}You can send follow-ups to the cloud agent here.
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit de1dfa2. Configure here.
| // NaN is converted to null by `JSON.stringify` so we type this | ||
| // as nullable and transform it back to NaN to allow invalid data | ||
| // to be saved. | ||
| timeout: z | ||
| .number() | ||
| .nullable() | ||
| .transform((value) => value ?? NaN), |
There was a problem hiding this comment.
This is up for debate. How do we want to handle invalid data and saving? Should we disable saving or make the schema tolerant to errors?
Personally, I would make the schema tolerant because it allows you to save unfinished work. It's more complex though than just disabling the save button.
WDYT?
There was a problem hiding this comment.
I have a similar opinion - as long as we clearly communicate which values/actions are invalid, the use should be able to save/open invalid tests. This is already the case when you have an invalid URL in page.goto and in a few other places, though I didn't pay much attention to the schema - maybe it should have been adjusted as well.
| // NaN is converted to null by `JSON.stringify` so we type this | ||
| // as nullable and transform it back to NaN to allow invalid data | ||
| // to be saved. | ||
| timeout: z | ||
| .number() | ||
| .nullable() | ||
| .transform((value) => value ?? NaN), |
There was a problem hiding this comment.
I have a similar opinion - as long as we clearly communicate which values/actions are invalid, the use should be able to save/open invalid tests. This is already the case when you have an invalid URL in page.goto and in a few other places, though I didn't pay much attention to the schema - maybe it should have been adjusted as well.
| value={isNaN(timeout) ? '' : timeout} | ||
| onChange={(e) => { |
There was a problem hiding this comment.
I'd add min={0} to the props.
There was a problem hiding this comment.
For some reason I can't comment on the first comment you made, but I think this is where you want two set of types: one for the document schema, which is fault tolerant, and another one that's a validated and strictly typed version.
And this is not the same as the PrefixTestActionSchema PrefixRunnerActionSchema.

Description
This PR adds support for the
page.waitForTimeoutaction to both the test editor and debugger.When implementing session replay, I noticed that tests with only a
gotoaction would finish before anything meaningful is displayed. The feedback loop is supposed to be: run test -> check replay -> add next action. But if nothing is displayed you have no idea what to do next.So the idea is to add a timeout at the end of the test to make sure that content is loaded before the test ends.
Since I was going to have to implement all the codegen and everything I figured I'd just make it an editable action.
How to Test
page.waitForTimeoutChecklist
Related PR(s)/Issue(s)
Note
Medium Risk
Touches multiple layers (action schema parsing, runtime action tracking, and script codegen), so mismatches could break recording/replay or generated scripts. NaN/null coercion for
timeoutis subtle and may affect persisted action data if not handled consistently.Overview
Adds end-to-end support for the
page.waitForTimeoutbrowser action so it can be recorded, edited, validated, and code-generated.This introduces a new
wait-for-timeoutnode in the browser test model/IR and emitsawait page.waitForTimeout(<ms>)in generated scripts (with a new snapshot test). The runner schema and page proxy now recognize/track the action, including special handling to persist invalid/empty timeouts viaNaN.The Browser Test Editor UI gains a “Wait for timeout” action with a dedicated
TimeoutForm, registry entry, and display text in the validator.Reviewed by Cursor Bugbot for commit 3c6a3c1. Bugbot is set up for automated code reviews on this repo. Configure here.