fixhunt

biome nursery/noPlaywrightUselessAwait

labelled safe
Runs / 2026-10-17T20-47-06-687Z-lxf4-biome-nursery_noPlaywrightUselessAwaitbiome 2.5.152026-10-17 20:47 UTC8 programs × 2 round(s)15 programs, 6 fired, 6 fixed, 0 findings, 1 rejectedmodel cost $0.0035

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

idresulttitlehazard
r1-p002silent prediction offReordering of side effects in sequential locator callsIn 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-p003silent prediction off__proto__ access interfering with method resolution__proto__ keys, prototype access, Object.assign vs spread
r1-p004silent prediction offVar hoisting and re-declaration in loopvar hoisting and re-declaration inside loops or after use
r1-p005silent prediction offOrder of evaluation: duplicated expression with side effectsorder of evaluation when expressions are merged, duplicated or deleted (side effects run twice or never)
r1-p006silent prediction offComment attached to removed await linecomments attached to the removed or moved code
r1-p007silent prediction offASI hazard: leading parenthesis after semicolon-less await removalASI and semicolon-less code, leading ( or [ on the next line
r1-p008silent prediction offBroken .then() chain due to removed await on locatorIf 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-p001fix okComment loss on await tokenComments 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-p002invalidClass fields and static blocks interferenceclass fields, static blocks, private names, super calls
r2-p003fix okSide effect timing in argument evaluationIf 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-p004silent prediction offNull/undefined distinction with optional chainingnull/undefined distinction, optional chaining short-circuit, nullish vs falsy
r2-p005fix okLabel/break/continue and finally interactionlabel/break/continue control flow, finally blocks, early returns in nested blocks
r2-p006fix okSequential locator side-effect reorderingIn 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-p007fix okRegular expression lastIndex mutationregular expressions: escapes, unicode flag, lastIndex with the g flag
r2-p008fix okThis binding in extracted methodsthis 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