Skip to content

fix(helpers): restore substring matching in waitInUrl - #5682

Open
nlespiaucq wants to merge 1 commit into
codeceptjs:4.xfrom
nlespiaucq:fix/waitinurl-substring-match
Open

fix(helpers): restore substring matching in waitInUrl#5682
nlespiaucq wants to merge 1 commit into
codeceptjs:4.xfrom
nlespiaucq:fix/waitinurl-substring-match

Conversation

@nlespiaucq

@nlespiaucq nlespiaucq commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Motivation/Description of the PR

#5451 introduced resolveUrl() and wired it into waitInUrl alongside waitUrlEquals. For waitUrlEquals that is correct — a strict comparison genuinely needs the relative path resolved against the configured base url. For waitInUrl it silently turned a substring match into an origin-anchored one:

// before #5451
return currUrl.indexOf(urlPart) > -1                        // '/users' matches ANY url containing /users

// after #5451
const expectedUrl = resolveUrl(urlPart, this.options.url)   // '/users' -> 'https://app.example.com/users'
return currUrl.indexOf(expectedUrl) > -1                    // scheme + host + port must now match exactly
call before #5451 after #5451
I.waitInUrl('/users') after a redirect to another host/port/scheme passes times out
I.waitInUrl('user=test') on /info?user=test passes resolves to <base>/user=test, times out
I.waitInUrl('/app') with url: 'http://localhost' but live on 127.0.0.1 passes times out

This also contradicts the method's own docs — docs/webapi/waitInUrl.mustache ("Waiting for the part of the URL to match the expected") and docs/migration-4.md, which literally states I.waitInUrl('/users') "matches any URL containing /users". The docs described the old behavior; only the code changed.

Fix: waitInUrl compares against the raw urlPart again in Playwright, Puppeteer and WebDriver, and reports it unresolved in the timeout message. resolveUrl imports stay — waitUrlEquals still uses them.

This is strictly more permissive than current 4.x for every realistic input (resolveUrl('/x', base) always ends in /x, so anything matching the resolved form also matches the raw form), so no currently-passing usage regresses.

waitUrlEquals is deliberately untouched. #5451 also tightened it from substring to strict equality in Playwright/Puppeteer; that matches the method's name, its docs and WebDriver's long-standing behavior, so it is out of scope here.

No issue was filed for this — it was found while upgrading. Resolves the waitInUrl half of the behavior change in #5451.

Applicable helpers:

  • Playwright
  • Puppeteer
  • WebDriver
  • REST
  • FileHelper
  • Appium

Appium extends WebDriver and does not override waitInUrl, so it inherits the fix without a code change of its own.

Applicable plugins:

  • aiTrace
  • autoDelay
  • autoLogin
  • customLocator
  • pause
  • coverage
  • heal
  • retryFailedStep
  • screenshot
  • selenoid
  • stepTimeout
  • subtitles

Type of change

  • 🔥 Breaking changes
  • 🚀 New functionality
  • 🐛 Bug fix
  • 🧹 Chore
  • 📋 Documentation changes/updates
  • ♨️ Hot fix
  • 🔨 Markdown files fix - not related to source code
  • 💅 Polish code

Checklist:

  • Tests have been added
  • Documentation has been added (Run npm run docs)
  • Lint checking (Run npm run lint)
  • Local tests are passed (Run npm test)

Notes on the two unchecked boxes:

  • npm run docs currently crashes on 4.x before this PR as well — TypeError: Cannot read properties of undefined (reading 'members') at runok.cjs:417 in docsAppium. Unrelated to this change and not fixed here. Regeneration is not needed anyway: docs/webapi/waitInUrl.mustache is unchanged (it already described the restored behavior), so the generated docs/helpers/*.md are byte-identical. docs/migration-4.md is hand-written and was updated in this PR.
  • npm test was not run end to end; test:unit was (769 passing, 0 failing), but test:rest and test:runner were not. The helper suites that actually cover this change were run instead — see below.

Testing

The existing test placed its assert only inside catch, so it passed vacuously whenever the wait unexpectedly succeeded — which is how this regression shipped. It now fails on a missing timeout, and a new case pins the bug.

Reverting only the helper change reproduces the regression in its own words:

Error: expected url to include http://127.0.0.1:8000/user=test, but found http://127.0.0.1:8000/info?user=test

Verified locally against the PHP test app:

suite result
Playwright — waitInUrl / waitUrlEquals / waitCurrentPathEquals 6 passing
Puppeteer — same 6 passing
WebDriver — same 6 passing
Playwright / Puppeteer — url, path and tab-switching tests 30 + 28 passing
npm run test:unit 769 passing, 0 failing
npm run lint clean

The WebDriver leg was run through webdriverio's self-managed chromedriver rather than the Selenium container, which cannot complete its BiDi websocket handshake through Docker Desktop on macOS; CI covers the Selenium path.

🤖 Generated with Claude Code

codeceptjs#5451 wired resolveUrl() into waitInUrl alongside waitUrlEquals. For
waitUrlEquals that is correct — a strict comparison needs the relative path
resolved against the configured base url. For waitInUrl it silently turned a
documented substring match into an origin-anchored one:

  const expectedUrl = resolveUrl(urlPart, this.options.url)  // '/users' -> 'https://app.example.com/users'
  return currUrl.indexOf(expectedUrl) > -1                   // scheme+host+port must now match exactly

So I.waitInUrl('/users') breaks after a redirect to a different host, port or
scheme, and a partial match such as I.waitInUrl('user=test') resolves to
<base>/user=test and never matches /info?user=test.

Compare against the raw urlPart again in Playwright, Puppeteer and WebDriver.
waitUrlEquals is left untouched.

The existing test asserted only inside catch, so it passed vacuously whenever
the wait unexpectedly succeeded — which is how this shipped. It now fails on a
missing timeout, plus a new case pinning the regression.

docs/migration-4.md listed waitInUrl among the methods that resolve relative
urls while its own example said the opposite; corrected.

Co-authored-by: Claude <claude@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant