biome nursery/noPlaywrightUselessAwait
labelled safe
Analysis Nemotron 3 Super
The rule flags unnecessary `await` keywords before Playwright methods that return synchronous values, such as locator creation or synchronous expect matchers.
Fix: Remove the `await` keyword from the expression, leaving the method call unchanged.
10 hazards
- Rewriting `await page.locator(...)` removes the await but if the locator method were overridden to return a Promise in a subclass or mock, the code would now ignore the Promise and proceed synchronously, breaking async logic.
- If the expression has side effects (e.g., `await page.locator(fn())`), removing `await` changes when `fn()` is evaluated relative to surrounding code, potentially altering timing-dependent behavior.
- In a sequence like `await page.locator(A); await page.locator(B);`, removing awaits could reorder execution if `A` or `B` have side effects, violating intended sequencing.
- If the awaited expression is used in a boolean context like `if (await page.locator(...))`, removing `await` treats a Locator object as truthy instead of awaiting a potential Promise, changing the condition's evaluation.
- When the awaited call is part of a larger expression such as `x = await page.locator(...)`, removing `await` assigns a Locator instead of a resolved Promise, breaking downstream code expecting a Promise.
- If the locator method is accessed via a dynamic property (e.g., `await page[methodName]()`), the fix assumes it returns a sync value, but if the property resolves to an async method, the await removal breaks the code.
- In try/catch blocks, removing `await` from a Playwright call that actually returns a Promise (due to monkey-patching or type mismatch) causes the Promise to be unhandled, leading to uncaught rejections instead of being caught.
- Comments placed on the `await` token (e.g., `await/* TODO */ page.locator(...)`) may be lost during the rewrite if the tool does not preserve inline comments on removed tokens.
- If the code relies on the awaited expression returning a Promise for `.then()` chaining (e.g., `await page.locator(...).then(...)`), removing `await` breaks the chain by applying `.then()` to a Locator.
- In an async function, removing `await` from a call that returns a Promise (even if incorrectly flagged) turns the call into a fire-and-forget operation, potentially causing race conditions or unhandled rejections.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p002 | silent prediction off | Reordering of side effects in sequential locator calls | In a sequence like `await page.locator(A); await page.locator(B);`, removing awaits could reorder execution if `A` or `B` have side effects, violating intended sequencing. |
| r1-p003 | silent prediction off | __proto__ access interfering with method resolution | __proto__ keys, prototype access, Object.assign vs spread |
| r1-p004 | silent prediction off | Var hoisting and re-declaration in loop | var hoisting and re-declaration inside loops or after use |
| r1-p005 | silent prediction off | Order of evaluation: duplicated expression with side effects | order of evaluation when expressions are merged, duplicated or deleted (side effects run twice or never) |
| r1-p006 | silent prediction off | Comment attached to removed await line | comments attached to the removed or moved code |
| r1-p007 | silent prediction off | ASI hazard: leading parenthesis after semicolon-less await removal | ASI and semicolon-less code, leading ( or [ on the next line |
| r1-p008 | silent prediction off | Broken .then() chain due to removed await on locator | If the code relies on the awaited expression returning a Promise for `.then()` chaining (e.g., `await page.locator(...).then(...)`), removing `await` breaks the chain by applying `.then()` to a Locato |
| r2-p001 | fix ok | Comment loss on await token | Comments placed on the `await` token (e.g., `await/* TODO */ page.locator(...)`) may be lost during the rewrite if the tool does not preserve inline comments on removed tokens. |
| r2-p002 | invalid | Class fields and static blocks interference | class fields, static blocks, private names, super calls |
| r2-p003 | fix ok | Side effect timing in argument evaluation | If the expression has side effects (e.g., `await page.locator(fn())`), removing `await` changes when `fn()` is evaluated relative to surrounding code, potentially altering timing-dependent behavior. |
| r2-p004 | silent prediction off | Null/undefined distinction with optional chaining | null/undefined distinction, optional chaining short-circuit, nullish vs falsy |
| r2-p005 | fix ok | Label/break/continue and finally interaction | label/break/continue control flow, finally blocks, early returns in nested blocks |
| r2-p006 | fix ok | Sequential locator side-effect reordering | In a sequence like `await page.locator(A); await page.locator(B);`, removing awaits could reorder execution if `A` or `B` have side effects, violating intended sequencing. |
| r2-p007 | fix ok | Regular expression lastIndex mutation | regular expressions: escapes, unicode flag, lastIndex with the g flag |
| r2-p008 | fix ok | This binding in extracted methods | this binding: arrow vs function, methods extracted from objects, call/apply/bind |
Sources
r1-p002 · Reordering of side effects in sequential locator calls
1// Hazard 2: Side effect order matters; removal may reorder
2let log = '';
3const sideEffect = (msg) => { log += msg; return { click: () => {} } };
4// Simulate two locator-like calls with side effects in args
5{ const temp = sideEffect('A'); await temp.click(); }
6{ const temp = sideEffect('B'); await temp.click(); }
7console.log('Log:', log);
Log: AB
r1-p003 · __proto__ access interfering with method resolution
1// Hazard 3: __proto__ tampering affects object shape
2const base = { locator: () => ({ click: () => console.log('real') }) };
3const fake = { __proto__: base, locator: () => ({ click: () => console.log('fake') }) };
4// Rule sees: await fake.locator() -> but __proto__ may interfere
5{ const l = fake.locator(); await l.click(); }
6{ const l = fake.locator(); l.click(); } // No await - still works? but we test if rule fires
7console.log('Test done');
fake fake Test done
r1-p004 · Var hoisting and re-declaration in loop
1// Hazard 4: var hoisting with re-declaration
2for (let i = 0; i < 2; i++) {
3 var loc = { click: () => console.log('iter', i) }; // hoisted, redeclared
4 await loc.click(); // rule fires here
5}
6console.log('After loop:', typeof loc); // loc exists due to hoisting
iter 0 iter 1 After loop: object
r1-p005 · Order of evaluation: duplicated expression with side effects
1// Hazard 5: Side effect in argument runs twice if await removed and expr duplicated
2let count = 0;
3const getArg = () => { count++; return '.el'; };
4// Rule sees: await page.locator(getArg()) - if it duplicates the call...
5{ const l = { click: () => console.log('count', count) }; await l.click(); }
6// We simulate: if await removed and expr duplicated, getArg might run twice
7// But we can't duplicate - so we make the hazard visible by having the call itself have side effect
8console.log('Final count:', count); // Should be 1 if await kept, but we show it's used
count 0 Final count: 0
r1-p006 · Comment attached to removed await line
1// Hazard 6: Comment on the line with await
2// This comment should stay after fix
3await { click: () => console.log('hi') }.click(); // keep this comment
4console.log('After');
hi After
r1-p007 · ASI hazard: leading parenthesis after semicolon-less await removal
1// Hazard 7: ASI risk - if await removed and no semicolon, next line '(' is absorbed
2let x = 1
3(await { click: () => console.log(x) }).click() // If no semicolon after 1, this is: 1(await...)
4console.log('Survived')
r1-p008 · Broken .then() chain due to removed await on locator
1// Hazard 8: Removing await breaks .then() chain
2let result = 'none';
3// Simulate locator that returns a promise-like thenable
4const loc = {
5 then: (cb) => { cb('resolved'); return loc; }
6};
7// Normally: await loc.then(v => { result = v; });
8// But rule sees: await loc - and removes await, leaving loc.then(...)
9// So we do: await loc; then .then? No - we make it so the code is: await loc.then(...)
10// But rule only fires on await loc, not await loc.then...
11// So we need: await loc -> but we want to test if removing await breaks a chain that comes AFTER
12// Let's restructure: code is: await loc; loc.then(...) -> but that's two statements
13// Instead: make it one expression: (await loc).then(...) -> rule doesn't fire on that
14// We need the rule to fire on a subexpression that is used in a chain
15// Final attempt: we make the locator call itself be chained, but rule fires on the await
16console.log('Skipped - complex');
Skipped - complex
r2-p001 · Comment loss on await token
1let logged = '';
2function log(msg) { logged += msg + '|'; }
3await/* TODO */ page.locator('.elem');
4log('done');
5console.log(logged);
--- orig.js 2026-10-17 16:47:28.399853276 -0400 +++ fixed.js 2026-10-17 16:47:28.672859676 -0400 @@ -1,5 +1,5 @@ let logged = ''; function log(msg) { logged += msg + '|'; } -await/* TODO */ page.locator('.elem'); +page.locator('.elem'); log('done'); console.log(logged);
r2-p002 · Class fields and static blocks interference (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-47-0)
1class Test {
2 static x = 0;
3 static {
4 await/* mark */ page.locator('.static');
5 Test.x = 1;
6 }
7}
8console.log(Test.x);
r2-p003 · Side effect timing in argument evaluation
1let called = false;
2function fn() { called = true; return '.dynamic'; }
3console.log('before:', called);
4await page.locator(fn());
5console.log('after:', called);
--- orig.js 2026-10-17 16:47:28.892864833 -0400 +++ fixed.js 2026-10-17 16:47:29.167871275 -0400 @@ -1,5 +1,5 @@ let called = false; function fn() { called = true; return '.dynamic'; } console.log('before:', called); -await page.locator(fn()); +page.locator(fn()); console.log('after:', called);
before: false
r2-p004 · Null/undefined distinction with optional chaining
1let obj = { locator: () => ({ click: () => 'ok' }) };
2let result = obj?.locator?.()?.click();
3console.log(result);
4obj = null;
5result = await/* mark */ obj?.locator?.()?.click();
6console.log(result);
ok undefined
r2-p005 · Label/break/continue and finally interaction
1outer: {
2 try {
3 await/* mark */ page.locator('.test');
4 break outer;
5 } finally {
6 console.log('finally ran');
7 }
8}
9console.log('after');
--- orig.js 2026-10-17 16:47:29.567880640 -0400 +++ fixed.js 2026-10-17 16:47:29.828886751 -0400 @@ -1,6 +1,6 @@ outer: { try { - await/* mark */ page.locator('.test'); + page.locator('.test'); break outer; } finally { console.log('finally ran');
finally ran
r2-p006 · Sequential locator side-effect reordering
1let order = '';
2function A() { order += 'A'; return '.a'; }
3function B() { order += 'B'; return '.b'; }
4await page.locator(A());
5await page.locator(B());
6console.log(order);
--- orig.js 2026-10-17 16:47:29.997890708 -0400 +++ fixed.js 2026-10-17 16:47:30.270897093 -0400 @@ -1,6 +1,6 @@ let order = ''; function A() { order += 'A'; return '.a'; } function B() { order += 'B'; return '.b'; } -await page.locator(A()); -await page.locator(B()); +page.locator(A()); +page.locator(B()); console.log(order);
r2-p007 · Regular expression lastIndex mutation
1const regex = /a/g;
2regex.lastIndex = 0;
3console.log('match1:', regex.exec('aa'));
4await/* mark */ page.locator(regex);
5console.log('match2:', regex.exec('aa'));
--- orig.js 2026-10-17 16:47:30.436900975 -0400 +++ fixed.js 2026-10-17 16:47:30.709907359 -0400 @@ -1,5 +1,5 @@ const regex = /a/g; regex.lastIndex = 0; console.log('match1:', regex.exec('aa')); -await/* mark */ page.locator(regex); +page.locator(regex); console.log('match2:', regex.exec('aa'));
match1: [ 'a', index: 0, input: 'aa', groups: undefined ]
r2-p008 · This binding in extracted methods
1const obj = {
2 value: 42,
3 getValue() { return this.value; }
4};
5const fn = obj.getValue;
6console.log('direct:', obj.getValue());
7console.log('extracted:', fn());
8await/* mark */ page.locator('.dummy');
9console.log('after await:', fn.bind(obj)());
--- orig.js 2026-10-17 16:47:30.896911733 -0400 +++ fixed.js 2026-10-17 16:47:31.174918229 -0400 @@ -5,5 +5,5 @@ const fn = obj.getValue; console.log('direct:', obj.getValue()); console.log('extracted:', fn()); -await/* mark */ page.locator('.dummy'); +page.locator('.dummy'); console.log('after await:', fn.bind(obj)());
direct: 42