Skip to content

test: adapt E2E suite to v4 and fix regressions it caught - #4012

Open
B4nan wants to merge 15 commits into
v4from
claude/e2e-tests-v4-0d3568
Open

test: adapt E2E suite to v4 and fix regressions it caught#4012
B4nan wants to merge 15 commits into
v4from
claude/e2e-tests-v4-0d3568

Conversation

@B4nan

@B4nan B4nan commented Aug 11, 2026

Copy link
Copy Markdown
Member

Gets the E2E test suite running against v4. The suite hadn't been run since the v4 rewrite and everything failed on startup. After these changes the MEMORY run passes locally end to end, and getting there surfaced a few real regressions in the packages themselves.

Package fixes

  • LinkeDOMCrawler's enqueueLinks helper referenced the global document (which doesn't exist in Node) instead of the parsed window, so every call crashed at runtime.
  • ErrorSnapshotter.saveHTMLSnapshot() returned the record key with a v3-style .html suffix, so the follow-up getPublicUrl() lookup missed and firstErrorHtmlUrl never made it into the crawler statistics.
  • JSDOMCrawlingContext/LinkeDOMCrawlingContext didn't override enqueueLinks, exposing the strict urls-required signature even though the runtime helper extracts URLs from the parsed document.
  • LinkeDOMCrawler can now be constructed without arguments, like the other crawlers.

E2E suite changes

  • Bumped the pinned apify SDK to 4.0.0-beta.22 (beta.19 imports snakeCaseToCamelCase from @crawlee/utils, which no longer exists there).
  • Adapted tools.mjs to the fs-storage on-disk layout (__default__ aliases, extensionless key-value records) and to the @crawlee/utils exports split.
  • Migrated test actors to the v4 APIs: the logger option with ApifyLogAdapter instead of log, hooks reading gotoOptions from the crawling context, session.setCookie(), a custom SessionPool instead of sessionPoolOptions, the WHATWG Response returned by sendRequest, registerDeferredCleanup for dataset writes that must survive a throwing handler, and explicit enqueue strategies now that include globs are ANDed with the default same-hostname strategy.
  • The ignore-ssl test now configures TLS verification on the http client, because the crawler-level ignoreSslErrors option is not wired to the default client in v4. That dangling option deserves a separate fix or removal, since it currently does nothing.
  • The impit test pins session fingerprints, since the random default fingerprint overrides the client's browser impersonation.
  • Added ES2022 to the actor tsconfigs' lib (a bare ["DOM"] drops the ES lib and broke compilation on ErrorOptions).
  • Skipped the zero-concurrency queue test: it stages a stuck queue through the v3 client-side inProgress set, which the rewritten queue doesn't have.
  • Fixed the camoufox fetch retry loop fetching 5x even on success, and removed a duplicate apify dependency key that silently downgraded the curl-impersonate actor to SDK v3.
  • Commented out the LOCAL storage matrix entry in the workflow, as @apify/storage-local doesn't support v4.

B4nan added 2 commits August 11, 2026 18:11
- `LinkeDOMCrawler`'s `enqueueLinks` helper referenced the non-existent global
  `document` instead of the parsed window, crashing every call at runtime
- `ErrorSnapshotter.saveHTMLSnapshot()` returned the record key with a v3-style
  `.html` suffix, so the subsequent `getPublicUrl()` lookup missed and
  `firstErrorHtmlUrl` never made it into the crawler statistics
- `JSDOMCrawlingContext`/`LinkeDOMCrawlingContext` did not override
  `enqueueLinks`, exposing the strict urls-required signature even though the
  runtime helper extracts URLs from the parsed document
- `LinkeDOMCrawler` constructor now accepts no arguments, like the other crawlers
- bump the pinned `apify` SDK to 4.0.0-beta.22 (beta.19 imports
  `snakeCaseToCamelCase` from `@crawlee/utils`, which no longer exists)
- adapt `tools.mjs` to the fs-storage on-disk layout (`__default__` aliases,
  extensionless key-value records) and the `@crawlee/utils` exports split
- migrate test actors to the v4 APIs: `logger` option + `ApifyLogAdapter`
  instead of `log`, hooks receiving `gotoOptions` via the crawling context,
  `session.setCookie()`, custom `SessionPool` instead of `sessionPoolOptions`,
  WHATWG `Response` from `sendRequest`, `registerDeferredCleanup` for dataset
  writes that must survive a throwing handler, and explicit enqueue strategies
  now that `include` globs are ANDed with the default same-hostname strategy
- configure TLS verification on the http client in the ignore-ssl test (the
  crawler-level `ignoreSslErrors` option is not wired to the default client)
- pin session fingerprints in the impit test (the random default fingerprint
  overrides the client's browser impersonation)
- add ES2022 to actor tsconfig `lib` (bare `["DOM"]` dropped the ES lib and
  broke compilation on `ErrorOptions`)
- skip the zero-concurrency queue test (relies on the removed v3 client-side
  `inProgress` set) and fix the camoufox fetch retry loop re-fetching 5x
- comment out the LOCAL storage matrix entry (@apify/storage-local does not
  support v4 yet) and drop a duplicate `apify` dependency key that silently
  downgraded the curl-impersonate actor to SDK v3
@B4nan B4nan added t-tooling Issues with this label are in the ownership of the tooling team. adhoc Ad-hoc unplanned task added during the sprint. labels Aug 11, 2026
B4nan added 13 commits August 11, 2026 19:29
- rewrite the curl-impersonate actor's custom http client against the v4
  `BaseHttpClient` from `@crawlee/http-client` (implement `fetch()` returning
  a WHATWG `Response`; the base class now handles redirects/cookies/proxies),
  fix its tsconfig (`module` must be NodeNext to match the inherited
  `moduleResolution`), and drop stale uuid assertions the actor stopped pushing
- replace `browser.process()` (a Puppeteer-only API that does not exist on
  Playwright's `Browser`) with object-identity browser numbering in the
  stagehand-concurrent actor
- regenerate the jsdom/linkedom public API reports for the `enqueueLinks`
  context override
The LOCAL storage backend is disabled for v4 (no v4-compatible
@apify/storage-local yet), but every actor still depended on it, and its
better-sqlite3 native build fails on the platform image (musl, Node 24,
no prebuilt binary, no Python) - which broke `apify push` for the whole
PLATFORM matrix.
- drop `--no-optional` from actor Dockerfiles: v4's fs-storage depends on the
  `@crawlee/fs-storage-native` napi package whose per-platform binaries are
  optionalDependencies, so every platform actor crashed on startup with a
  missing `linux-x64-musl` binary
- add `@crawlee/types` to root devDependencies - workspace packages only get
  a root node_modules link when declared, so `tsc` in the e2e actors could not
  resolve it on a clean frozen install (local installs masked this)
- filter invalid header names (the parsed status line) out of curl-impersonate
  responses before constructing the fetch Response
- log the platform run status in the e2e harness when it is not SUCCEEDED
The napi loader of @crawlee/fs-storage-native throws at import time on
platforms without a published binary (e.g. linux musl, which the Apify
platform images use), and @crawlee/core imports @crawlee/fs-storage eagerly
through its service locator - so merely importing crawlee crashed there.
Require the native module on first storage use instead.
- wipe the base image's preinstalled node_modules before npm install in every
  actor Dockerfile (both stages of multistage builds) - the apify/actor-node
  image ships an SDK v3 install that survived our install and broke the -ts
  builds' type-checking against the copied v4 packages
- pin `apify` to 4.0.0-beta.22 in the actors instead of the floating next-v4
  dist-tag, so platform runs test the same SDK build as the local suite
Passing any scoped service to a crawler (e.g. just `logger`) created a fresh
ServiceLocator whose unset slots later materialized defaults - so a crawler
with a custom logger silently detached from a globally configured storage
backend (such as the platform storage set by the Apify SDK) and fell back to
the file-system one. The scoped locator now inherits the ambient locator's
already-set services for anything not explicitly provided.
- wipe the preinstalled node_modules of the browser base images before the
  `COPY --from=builder` in browser actor Dockerfiles - buildkit cannot copy
  the builder's file:-dep symlinks over the existing directories
- add skipLibCheck to the remaining -ts actor tsconfigs; the platform build
  otherwise type-checks dependency d.ts files (quick-lru) and core's d.ts
  reference to the type-only @standard-schema/spec, which is not installed
  in the actor's isolated node_modules
The Cloudflare challenge page is a 403, and `throwOnBlockedRequest` runs
after the post-navigation hooks - so the hook must return the reloaded
response via `handleCloudflareChallengeHook()`, otherwise the crawler
throws on the original challenge response before ever solving it.
The internal `userData.__crawlee` bag (carrying e.g. `skipNavigation`) is
non-enumerable on userData objects owned by a Request instance, so the
constructor's spread silently dropped it when a request was re-wrapped from
another instance's data - which happens on the platform request queue's
local-cache read path and lost `skipNavigation` there. Read the bag
explicitly before spreading.
Documents the v4 changes the e2e suite tripped over that were missing from
the guide: the `log` -> `logger` crawler option (with `ApifyLogAdapter` and
the missing `setLevel`), `include` patterns being ANDed with the enqueue
strategy instead of replacing it, the `@crawlee/utils` public//internal
entry point split, the removed `storageObject` property, and the switch of
the default HTTP client to impit with session-fingerprint-driven
impersonation.
- unit tests for the three package regressions fixed in this PR: `__crawlee`
  bag preservation on request re-wrap, ambient service inheritance in
  crawler-scoped service locators, and the error snapshotter record key
- promote `getServicesIfSet()` to the (non-exported) service locator
  interface, dropping the double cast at the call site
- guard the manual Cloudflare hook snippet in the upgrading guide against
  clobbering the response on no-challenge pages
- drop a dead `?? 'GET'` fallback and fix a copied Dockerfile comment in the
  curl-impersonate actor
…moufox test

The target hard-blocks datacenter IP ranges outright (block page instead of a
solvable challenge), and the verbose flag keeps the challenge detection visible
in the nightly logs for triage.
@B4nan
B4nan requested review from barjin and janbuchar August 12, 2026 00:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adhoc Ad-hoc unplanned task added during the sprint. t-tooling Issues with this label are in the ownership of the tooling team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants