Agent skill

Device Mode E2E

by rudderlabs in rudderlabs/rudder-sdk-js

End-to-end test a device-mode integration change locally. An agent skill from rudderlabs/rudder-sdk-js.

MITAuto-check passedTesting & QA

Install Device Mode E2E

skills CLI
$ npx skills add rudderlabs/rudder-sdk-js --skill device-mode-e2e -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install rudderlabs/rudder-sdk-js device-mode-e2e --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/rudderlabs/rudder-sdk-js.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/device-mode-e2e .claude/skills/device-mode-e2e && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
device-mode-e2e
GitHub stars
178
Token cost
~6.3k tokens
SKILL.md length
2,797 words
Files
8
Skills in repo
1
Repo updated
First seen
Licence
MIT

At a glance

End-to-end test a device-mode integration change locally. An agent skill from rudderlabs/rudder-sdk-js.

  • Works in 9 steps: Which CDN to load the SDK from? → Which verification mode? → Preflight — confirm the write key… → …
  • Asked to e2e test
  • SKILL.md covers What it verifies (and what it…, FIRST: ask the user two things…, Inputs and Prerequisites, plus 5 more sections
  • Runs JavaScript scripts from its folder; calls node, npm and nvm; reaches cdn.staging.rudderlabs.com and cdn.rudderlabs.com; needs WRITE_KEY

What it does

Device Mode E2E is an agent skill from rudderlabs/rudder-sdk-js. End-to-end test a device-mode integration change locally. Serves the SDK (local build or a released CDN), derives targeted events from the integration's implementation, generates a self-asserting HTML harness that loads the REAL connection from a write key alone, and runs it headed or headless. Reports a generic PASS/FAIL - SDK fetched sourceConfig, loaded the integration bundle, forwarded events, and actually sent data to the destination - plus a check that the destination config arrived in the SDK with the…

Its SKILL.md is about 6.3k tokens, which your agent loads only when the skill is triggered. The skill folder holds 8 other files (for example `harness/README.md`).

It sits in Testing & QA, covering End-to-end testing. It works with JavaScript. The repository describes itself as: JavaScript SDK for RudderStack - the Customer Data Platform for Developers. The licence is MIT.

When your agent uses it

  • Asked to e2e test
  • Verify device mode
  • Test my <destination changes
  • Test an integration against a different destination config

Example prompts

  • “e2e test”
  • “run e2e”
  • “verify device mode”
  • “/device-mode-e2e”

Requirements

  • Node.js
  • A credential in WRITE_KEY

Workflow steps

9 steps, taken from the step headings in SKILL.md.

  1. Which CDN to load the SDK from?
  2. Which verification mode?
  3. Preflight — confirm the write key actually has the integration connected
  4. Build + serve the SDK, integrations, AND plugins — only for cdn: local
  5. Wait for the servers to be ready (local only)
  6. Derive targeted test cases from the implementation (the LLM step)
  7. Write the run config
  8. Generate the page, then pick a mode
  9. Interpret + confirm — REPRODUCE THE FULL REPORT IN YOUR REPLY

What it can do on your machine

Read from SKILL.md and the folder at commit 8b8757a. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships script files (JavaScript), which the agent can run.

    Shell commands in SKILL.md call:

    • node
    • npm
    • nvm
    • npx
    • git
    • curl
    • jq

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • cdn.staging.rudderlabs.com
    • cdn.rudderlabs.com
    • cdn.dev.rudderlabs.com
    • api.rudderstack.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names these keys or tokens, usually read from environment variables:

    • WRITE_KEY

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Device Mode E2E loads about 6.3k tokens when it runs. Until then it costs about 234 tokens; SKILL.md has 2,797 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~234
When it runs · the whole SKILL.md, loaded when a task matches
~6.3k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from rudderlabs/rudder-sdk-js at commit 8b8757a, republished under its MIT licence (© rudderlabs). 2,797 words, ~6,253 tokens.

Download SKILL.mdSave it as .claude/skills/device-mode-e2e/SKILL.md (or your agent's skills folder). This skill also uses 7 other files; get the full folder from GitHub.
name
device-mode-e2e
description
End-to-end test a device-mode integration change locally. Serves the SDK (local build or a released CDN), derives targeted events from the integration's implementation, generates a self-asserting HTML harness that loads the REAL connection from a write key alone, and runs it headed or headless. Reports a generic PASS/FAIL - SDK fetched sourceConfig, loaded the integration bundle, forwarded events, and actually sent data to the destination - plus a check that the destination config arrived in the SDK with the shape rudder-integrations-config says it should. Can vary a destination's config per run (configOverride) without editing the dashboard. Use when asked to "e2e test", "run e2e", "verify device mode", "test my <destination> changes", or to test an integration against a different destination config, for any browser device-mode integration. Confirming events inside the destination dashboard stays manual.
argument-hint
<Integration> <writeKey> [v3|v1.1]

Device-mode e2e harness

Objective: remove the manual effort of hand-writing an HTML page, opening it, and reading the Network tab to check whether a device-mode integration change actually forwards events. Given a write key alone, this harness builds and serves the SDK, fires targeted events at the real connection, and reports a generic PASS/FAIL. It is destination-agnostic: no integration-specific verification is baked in.

What it verifies (and what it does not)

Generic, automated (any device-mode integration):

  • the SDK fetched the real sourceConfig from the write key and got a 2xx,

  • it loaded the device-mode integration bundle (/js-integrations/),

  • ready() fired and every event was forwarded without throwing,

  • DATA was actually sent to a third-party (non-localhost, non-RudderStack) host — a fetch/XHR/ beacon/pixel, not merely a native-SDK <script> load. "native SDK asset loaded" is reported separately as info; only "data sent" counts as delivery.

  • the destination config arrived in the SDK with the shape the control-plane contract says — for each key rudder-integrations-config lists for a web device-mode connection and that was delivered, the value has the expected type. A key stored per source type ({ web: … }) must arrive resolved to its inner value, not as an object; this catches delivery bugs the SDK would otherwise swallow. Reported as warnings by default; configContract: "strict" fails the run on a type mismatch. Keys that are listed but absent are reported as missing and are informational only — most destination settings are optional, so an unset one legitimately never reaches the SDK. Keys delivered but not listed appear as unexpected, also informational. Schema conformance is not checked here — that is rudder-integrations-config's own CI job.

Captured console/page errors (console.error, onerror, unhandledrejection) are shown as warnings — they do not fail the run (third-party SDKs log noise).

It also SHOWS you the actual outgoing requests — method, URL, query params, and body — so you can confirm your fix put the right value in the payload (say, the SDK's messageId arriving as the destination's own event id), not just that a request fired. You can optionally add generic expectations to turn a specific value check into an automated PASS/FAIL (see step 5 and step 7).

Manual (out of scope — inherently per-destination): confirming the events arrived inside the destination's dashboard. The harness ends by reminding you to check it. Do not try to automate this in the generic skill.

FIRST: ask the user two things (do not skip, do not choose for them)

Before building or running anything, ask the user and wait for their answers:

1. Which CDN to load the SDK from?
  • local (default) — the localhost dev servers, i.e. your uncommitted code. Requires the build + serve steps.
  • staging (https://cdn.staging.rudderlabs.com) / production (https://cdn.rudderlabs.com) / dev (https://cdn.dev.rudderlabs.com) — the released SDK for that write key. No build or serve needed — skip steps 2 & 3 (the step-1 preflight still applies). Use this to reproduce a customer issue or verify a config-only change against live code.
  • a specific/custom CDN — any https://… base; ask if they want one.

Present those defaults and let them pick (or name a custom base). Set cdn (and optional variant: modern/legacy) in the config accordingly; local keeps the explicit localhost URLs.

2. Which verification mode?
  • Interactive — you build/serve (or use the CDN), open the harness page in their browser, and they click ▶ Run tests and inspect the report. Best for onboarding / large changes.
  • Report — you run it automatically and hand back the report (test cases used, actual outgoing requests, and an "expected on the <destination> dashboard" summary). Best for minimal / targeted changes (e.g. eventId-from-messageId).

You may recommend based on the change ("minimal → Report", "config-only → production CDN"), but the user decides. Only skip a question if they already answered it (or said "just pick"). Do not silently default to Report, do not run headless without being asked, and do not assume local if they might want to test the released SDK — surface the choice.

Inputs

  • Integration — the folder name under packages/analytics-js-integrations/src/integrations/. Used to locate the source for test-case derivation and to match the loaded bundle name. Never used for verification logic.
  • writeKey (required) — a workspace source that has this integration connected as a device-mode destination. The SDK resolves the real connection from this alone.
  • SDK version — v3 (default) or v1.1.

Prerequisites

  • The repo's Node version — pinned in .nvmrc at the repo root. The headless runner needs the global WebSocket (Node ≥ 22), and your shell may default to an older Node; switch first and verify:

    bash
    nvm use && node -v      # picks up .nvmrc; or point the scripts at an explicit Node >= 22 binary
  • Chrome/Chromium installed. The runner probes standard locations; if it can't find yours, set CHROME_PATH. On macOS the default is:

    bash
    export CHROME_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"

If Node ≥ 22 or Chrome is unavailable, use the zero-dep fallback: serve the directory that holds only the generated page (<temp>/page, see step 6) with any static server, open it in your own browser, click ▶ Run tests, and read the on-page PASS/FAIL table — no runner needed. (run-cdp.mjs is only for the automated report flow.)

Never point a directory server at the dir holding the run config or preflight.json. A native destination SDK loaded by the page runs in the page's origin, so it could fetch those sibling files and read the write key / destination config. run-cdp.mjs serves the single HTML file for exactly this reason; a plain static server does not, so give it a directory with nothing else in it.

Steps

1. Preflight — confirm the write key actually has the integration connected

Do this first — it catches "zero destinations" in seconds and is cache-busted (so it reflects a just-connected destination, not a stale control-plane copy):

bash
node .claude/skills/device-mode-e2e/harness/preflight.mjs \
  --writeKey <WRITE_KEY> --integration <Integration> --json <temp>/preflight.json

Exit 0 = connected & enabled. Exit 1 = not connected / disabled / zero destinations — fix the connection first. (Add --configUrl for a non-default control plane.)

--json writes the resolved destinations — id, enabled, connectionMode and their live config. It is required only for a config override (step 5), but always worth passing: the printed id is what an override is keyed by.

Use this layout for the temp files (it keeps secrets off the page's own origin — see Security):

text
<temp>/preflight.json     # destination config — NOT served
<temp>/run.json           # run config, embeds the write key — NOT served
<temp>/page/harness.html  # the only file any server is pointed at
2. Build + serve the SDK, integrations, AND plugins — only for cdn: local

Skip this entire step (and step 3) when the user chose a CDN (staging/production/dev/custom) — the SDK, integrations, and plugins all load from that CDN, so there's nothing to build or serve. Go straight to derive test cases (step 4) → config with cdn (step 5) → run.

A v3 local run needs three servers. Run each in its own terminal (they keep watching/serving). Use the core/v1.1 :no-open scripts so their dev servers serve without opening a demo tab on top of the harness window (plain start/start:modern still open one):

bash
# 1) core SDK   → http://localhost:3001  (rsa.min.js under /cdn/<variant>/iife/)
cd packages/analytics-js && npm run start:modern:no-open

# 2) integrations → http://localhost:3005  (bundles under /cdn/<variant>/js-integrations/)
cd packages/analytics-js-integrations && npm run start:modern

# 3) plugins    → http://localhost:3002  (rsa-plugins.js under /cdn/<variant>/plugins/)
cd packages/analytics-js-plugins && npm run start:modern

Why the plugins server is required (this is the #1 gotcha). With the CDN snippet, when pluginsSDKBaseURL is omitted the SDK does not fall back to the public CDN — it derives the plugins URL from where the core script was loaded (localhost:3001), which does not serve plugins. Every plugin then silently fails to load — including the device-mode-destinations plugin, so no integration bundle is ever requested. You must serve the plugins package and set pluginsSDKBaseURL. (Same mechanism applies to destSDKBaseURL for integrations.)

Faster iteration: the integrations start rebuilds every bundle (minutes). To rebuild just one, use the targeted CLI build, then serve dist:

bash
cd packages/analytics-js-integrations
BROWSERSLIST_ENV=modern npm run build:integration:cli --intg=<Integration>
npx serve ./dist -p 3005 --cors

Base URLs for a v3 modern build (use legacy instead of modern for a legacy build):

  • sdkUrl = http://localhost:3001/cdn/modern/iife/rsa.min.js
  • destSDKBaseURL = http://localhost:3005/cdn/modern/js-integrations
  • pluginsSDKBaseURL = http://localhost:3002/cdn/modern/plugins

For v1.1, serve the legacy core (cd packages/analytics-v1.1 && npm run start:no-open; bundle rudder-analytics.min.js) and keep the integrations server; v1.1 does not use the remote plugins. Confirm ports/filenames from each dev-server log — they can differ from your setup.

3. Wait for the servers to be ready (local only)

The dev servers are not ready immediately (the integrations start builds everything first). Poll until all three bundles return 200 before running:

bash
for url in \
  http://localhost:3001/cdn/modern/iife/rsa.min.js \
  http://localhost:3005/cdn/modern/js-integrations/<Integration>.min.js \
  http://localhost:3002/cdn/modern/plugins/rsa-plugins.js ; do
  until curl -sf -o /dev/null "$url"; do echo "waiting: $url"; sleep 3; done
done
echo "all servers ready"
4. Derive targeted test cases from the implementation (the LLM step)

Read the integration source and the change under test, then synthesize a small, targeted set of events — do not blast every event type:

bash
# the implementation
packages/analytics-js-integrations/src/integrations/<Integration>/browser.js   # + utils.js, constants.js, nativeSdkLoader.js
# what changed
git -C <repo> diff -- packages/analytics-js-integrations/src/integrations/<Integration>/

Decide which SDK methods the integration implements (identify, track, page, group, alias) and which the change touches, and craft events exercising exactly those paths and payload shapes. Examples: trait handling changed → an identify with those traits; a new track property mapping → a track carrying that property; page-name handling → a page call.

5. Write the run config

Create a JSON config (full schema in harness/README.md). Write it to a temp dir outside the repo — it contains the write key in plaintext (see Security below):

cdn: local (build + serve) — give the three localhost URLs:

json
{
  "writeKey": "<WRITE_KEY>",
  "integration": "<Integration>",
  "sdkVersion": "v3",
  "cdn": "local",
  "sdkUrl": "http://localhost:3001/cdn/modern/iife/rsa.min.js",
  "destSDKBaseURL": "http://localhost:3005/cdn/modern/js-integrations",
  "pluginsSDKBaseURL": "http://localhost:3002/cdn/modern/plugins",
  "settleMs": 6000,
  "events": [
    /* the targeted events derived in step 4 */
  ],
  "expectations": [
    /* optional — see below */
  ]
}

cdn: staging | production | dev | <https base> — omit the three URLs; generate.mjs derives them as <base>/v3/<variant>/{rsa.min.js, js-integrations, plugins} (variant defaults to modern):

json
{
  "writeKey": "<WRITE_KEY>",
  "integration": "<Integration>",
  "cdn": "production",
  "settleMs": 6000,
  "events": [
    /* … */
  ]
}

Explicit sdkUrl/destSDKBaseURL/pluginsSDKBaseURL always override the derived ones. CDN auto-URLs are v3-only; for v1.1 on a CDN, set them explicitly. Leave dataPlaneUrl unset unless you also want cloud-mode. Set configUrl only for a non-default control plane (EU / self-hosted / staging CP).

Optional expectations turn a specific value check on the outgoing request into an automated PASS/FAIL (generic — no per-destination logic). Use them when the change is about what the request carries:

jsonc
// e.g. a fix that must forward a value into a specific request field
{ "description": "<what this proves>", "requestIncludes": "<expected value>" }
{ "description": "<what this proves>", "requestBodyPath": "<field>", "equals": "<value>" }

Both are evaluated only against DATA requests made after the events fired (a pre-event startup request can't satisfy them). requestIncludes matches a substring in any such request's URL or body; requestBodyPath+equals checks a JSON body field with strict === (so 1 ≠ "1" — match the type). For a user-controlled field, set a recognizable sentinel value in the event and assert it. For an SDK-generated id (messageId), you can't predict it — rely on the printed request body (step 7) instead of a static expectation.

Optional configOverride varies the destination config for one run — without editing the dashboard. Use it to exercise a config-dependent code path (a version switch, a new setting, a mapping toggle) across several runs against one connection:

jsonc
{
  // …the fields above, plus:
  "configOverride": { "<configKey>": "<value>" },
  "destinationId": "<id>", // from step 1; or…
  "preflightJson": "<temp>/preflight.json", // …let the id be resolved from the snapshot
}
  • Write it in the shape the SDK receives, i.e. values already resolved for the web source type: "<configKey>": "<value>", not { "<configKey>": { "web": "<value>" } }. The override merges into the config the integration reads, not into the control plane's stored form. Keys and values are whatever the destination under test defines — run dest-config.mjs (below) to list them.
  • It becomes the SDK's v3 sourceConfigurationOverride load option, so the real sourceConfig call still happens and is still verified — only the matched destination's config is adjusted afterwards.
  • The SDK shallow-merges it ({...real, ...override}), so a nested object is replaced wholesale — pass the whole object.
  • v3 only. sourceConfigurationOverride does not exist in v1.1; the combination is rejected.
  • It needs the destination id: give destinationId, or preflightJson to resolve it by name.
  • generate.mjs warns when an overridden key is not one the control plane sends for a web device-mode connection (db-config.json's destConfig.web + destConfig.defaultConfig) — such an override does not mirror production.
  • The delivered-config check reports what the control plane delivered, before your override is applied — so a mismatch there is about the real connection, not about what you overrode.

To see a destination's expected web shape, or to check a delivered config offline:

bash
# expected shape (which keys, which delivered type)
node .claude/skills/device-mode-e2e/harness/dest-config.mjs --integration <Integration>
# compare a delivered config (preflight --json, or result.deliveredConfig) against it
node .claude/skills/device-mode-e2e/harness/dest-config.mjs \
  --integration <Integration> --config <delivered-config.json> [--strict]   # --remote reads GitHub

The contract is read from a local checkout (integrationsConfigPath, env INTEGRATIONS_CONFIG_PATH, else ~/workspace/rudder-integrations-config), falling back to GitHub (integrationsConfigRef, default develop; integrationsConfigRemote: true forces it). With no contract available the run simply skips the check and says so.

Show full SKILL.md (970 more words)Show less
6. Generate the page, then pick a mode
bash
# generate the page into its OWN directory (it embeds the write key; nothing else goes in there)
mkdir -p <temp>/page
node .claude/skills/device-mode-e2e/harness/generate.mjs --config <temp>/run.json --out <temp>/page/harness.html

The page shows the test cases it will send and a ▶ Run tests button; it does nothing until run. Now run it in the mode the user chose in the first step (don't re-decide here):

Mode A — Interactive. The runner serves the page (from its temp dir) and opens a headed window where they click ▶ Run and read the on-page report. --no-autorun means it waits for the click instead of auto-running; it stays open until you Ctrl-C. Works the same for local and any CDN (the page loads the SDK from sdkUrl, independent of where the page itself is served):

bash
node .claude/skills/device-mode-e2e/harness/run-cdp.mjs --page <harness.html> --no-autorun

Do not copy the page into the repo (dist/) — it embeds the write key. The runner serves it from a temp dir, so nothing write-key-bearing lands in the working tree.

Mode B — Report (minimal changes, e.g. eventId-from-messageId). No clicking: the runner opens the page, auto-runs it, and prints the full report; reading the result is enough.

bash
# headed by default (a window opens and auto-runs); add --headless for CI / no display
node .claude/skills/device-mode-e2e/harness/run-cdp.mjs --page <harness.html> \
  --timeout 60000 --json /tmp/e2e-result.json
# --keep-open leaves the window up to inspect; --json dumps the full machine-readable result

Exits 0 (PASS) / 1 (FAIL). Both modes render the same three sections: Test cases used, Outgoing device-mode data requests (method/URL/query/body), and Expectations. Headed vs --headless changes nothing in the report — the same detail is printed either way.

7. Interpret + confirm — REPRODUCE THE FULL REPORT IN YOUR REPLY

run-cdp.mjs prints the report to the terminal, which the user does not see — so in Report mode you must reproduce the whole report in your reply, not a one-line "PASS". Paste back, verbatim or lightly formatted:

  • Test cases used — every event fired,
  • Outgoing device-mode data requests — each request with its body (this is the proof of the fix),
  • Expectations — each PASS/FAIL,
  • the overall PASS/FAIL,
  • and the "expected on the <destination> dashboard" summary (below).

Reducing it to "it passed" defeats the whole point — the detail is the deliverable. (Tip: pass --json <path> and read that file if you need the structured data to build the summary.)

The Outgoing device-mode data requests bodies are where the fix is confirmed — the payload shows the value your change was supposed to put there.

To reduce the user to a reader (report-first flow): after a Mode-B run, produce an "Expected on the <destination> dashboard" summary so they only have to glance at the dashboard. Build it by reading the integration's mapping (browser.js: identify→attributes, track→custom event, ecommerce→purchase, alias→id) together with the captured request bodies, and write, per test case, what should appear. Shape it like this, using the mapping the integration actually implements:

  • identify <userId> → a profile with the traits you sent
  • track "<event name>" → an event of that name with those properties
  • track "<ecommerce event>" → whatever the integration maps it to (e.g. a purchase)
  • page → however that integration represents a page view

This narrative is LLM-authored at report time — it needs no destination code in the harness (the request bodies already contain the attributes/events/purchases). On PASS the RudderStack side is verified; the only remaining manual step is glancing at the dashboard to confirm arrival. Fully automating even that would need a destination API token (opt-in per-destination verifier) — out of scope for the generic skill.

Version swap

Only the core bundle differs between v3 and v1.1: point sdkUrl at the respective served core (rsa.min.js vs rudder-analytics.min.js) and set sdkVersion. Integration bundles are shared; pluginsSDKBaseURL applies to v3 only.

Security

The report, the --json result and the preflight --json snapshot contain the destination's delivered config (which can include destination API keys), and the generated page and run config embed the write key in plaintext. Keep them out of the repo and off shared/hosted pages: write them to a temp dir outside the working tree and delete them when done. Never commit them or paste the page URL into a shared location.

Keep the page in its own directory (<temp>/page/harness.html), with the run config and preflight.json one level up. Anything served next to the page is readable by every script running in the page's origin — including the destination's own SDK. run-cdp.mjs serves just the one file and sets no CORS header; a static server used for the fallback needs the isolated directory to get the same property.

Troubleshooting

  • integration bundle loaded: FAIL / no bundle requested at all — usually the plugins server is missing or pluginsSDKBaseURL is unset (see step 2's "Why"): the device-mode-destinations plugin never loads, so no integration is requested. Confirm <pluginsSDKBaseURL>/rsa-plugins.js and <destSDKBaseURL>/<Integration>.min.js both return 200. Check modern vs legacy matches your build.
  • sourceConfig fetched: FAIL — wrong write key, or a non-default control plane (set configUrl).
  • DATA sent: FAIL — the integration initialized but no data was sent after the events; often the source has no such device-mode destination connected (run the step-1 preflight), or the change suppressed delivery. (A native-SDK <script> load alone does not count as data.)
  • Preflight shows a destination you just connected as missing — control-plane cache lag: a freshly connected destination can take a few minutes to propagate. The preflight/SDK calls are cache-busted, so re-run after a short wait. To check by hand with a cache-buster:
    bash
    curl -s "https://api.rudderstack.com/sourceConfig/?writeKey=<KEY>&p=npm&v=3&t=$(date +%s)" | jq '.source.destinations[].destinationDefinition.name'
  • Timed out with no verdict — the SDK never became ready; open the page in a browser to see console errors. Increase --timeout / settleMs for slow native SDKs.
  • needs Node >= 22 — run nvm use to pick up the repo's .nvmrc version (see Prerequisites), or use the zero-dep fallback.
  • No Chrome found — set CHROME_PATH (see Prerequisites) or use the zero-dep fallback.

Files

  • harness/template.html — self-asserting page; owns all verification (window.__E2E_RESULT__).
  • harness/generate.mjs — fills the template from a config (pure templating; no verification logic).
  • harness/preflight.mjs — cache-busted sourceConfig check (is the integration connected?); --json snapshots destination ids + live configs for a config override.
  • harness/dest-config.mjs — reads a destination's contract (db-config.json + schema.json) from rudder-integrations-config, derives the expected web delivered shape, and compares a delivered config against it; also a standalone CLI.
  • harness/run-cdp.mjs — headless system-Chrome-over-CDP runner (no Puppeteer); reads the verdict.
  • harness/README.md — config schema, event shapes, extension notes.

© rudderlabs, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 7 other files in .claude/skills/device-mode-e2e of rudderlabs/rudder-sdk-js.

  • SKILL.md
  • harness/README.md
  • harness/dest-config.mjs
  • harness/generate.mjs
  • harness/preflight.mjs
  • harness/run-cdp.mjs
  • harness/template.html
  • harness/url-safety.mjs

Open the folder on GitHubat commit 8b8757a

Compare with similar skills

Device Mode E2E next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Device Mode E2E compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Device Mode E2E this skillrudderlabs/rudder-sdk-js178—~6.3kAutomated safety check: PassMIT
E2E Testcrowdin/crowdin-api-client-js139—~893Automated safety check: NotesMIT
Javascript Testing Expertsoftmaple/softmaple1591 repos~4kAutomated safety check: PassApache-2.0
Acceptance Test Authoringintent-driven-dev/intent-driven-template160—~2kAutomated safety check: PassMIT
Python Testing Patternsjh941213/my-cc-harness12617 repos~5.4kAutomated safety check: PassNone
Cypress Skillsickn33/agentic-awesome-skills47k1 repos~1.9kAutomated safety check: PassMIT

Similar skills

  • E2E Test

    crowdin/crowdin-api-client-js

    Run end-to-end tests against the real Crowdin or Crowdin Enterprise API using credentials from .env.

    139 GitHub stars~893 tokensUpdated yesterday
    Testing & QAAuto-check: notes
  • Javascript Testing Expert

    softmaple/softmaple

    Expert-level JavaScript testing skill focused on writing high-quality tests that find bugs, serve as documentation, and prevent regressions.

    159 GitHub starsUsed in 1 repo~4k tokens
    Testing & QAAuto-check passed
  • Acceptance Test Authoring

    intent-driven-dev/intent-driven-template

    A skill your agent uses when creating or modifying acceptance tests, configuring cucumber-js or behave runners, writing or refactoring step definitions, linting executable Gherkin specs, choosing an…

    160 GitHub stars~2k tokensUpdated 4 days ago
    Testing & QAAuto-check passed
  • Python Testing Patterns

    jh941213/my-cc-harness

    Implement comprehensive testing strategies with pytest, fixtures, mocking, and test-driven development.

    126 GitHub starsUsed in 17 repos~5.4k tokens
    Testing & QAAuto-check passed
  • Cypress Skill

    sickn33/agentic-awesome-skills

    Generates production-grade Cypress E2E and component tests in JavaScript or TypeScript.

    47k GitHub starsUsed in 1 repo~1.9k tokens
    Testing & QAAuto-check passed
  • Test Suite Analysis

    prime-radiant-inc/greenfield

    Layer 1 skill for extracting behavioral intelligence from test suites.

    292 GitHub stars~3.2k tokensUpdated 2 mo ago
    Testing & QAAuto-check passed

Works with

Categories

Questions about Device Mode E2E

What does Device Mode E2E do?

End-to-end test a device-mode integration change locally. An agent skill from rudderlabs/rudder-sdk-js. Device Mode E2E is an agent skill from rudderlabs/rudder-sdk-js. End-to-end test a device-mode integration change locally.

When should I use Device Mode E2E?

Device Mode E2E fits situations like: asked to e2e test; verify device mode; test my <destination changes; test an integration against a different destination config.

How do I install Device Mode E2E in Claude Code?

Run `npx skills add rudderlabs/rudder-sdk-js --skill device-mode-e2e -a claude-code`. Or copy the skill folder (.claude/skills/device-mode-e2e in rudderlabs/rudder-sdk-js) into .claude/skills/device-mode-e2e in your project. Claude Code loads it when a task matches its description.

How do I install Device Mode E2E in Codex?

Run `npx skills add rudderlabs/rudder-sdk-js --skill device-mode-e2e -a codex`. Or copy the skill folder (.claude/skills/device-mode-e2e in rudderlabs/rudder-sdk-js) into .agents/skills/device-mode-e2e in your project. Codex loads it when a task matches its description.

Can I use Device Mode E2E in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add rudderlabs/rudder-sdk-js --skill device-mode-e2e -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/device-mode-e2e, .gemini/skills/device-mode-e2e, .github/skills/device-mode-e2e and .opencode/skills/device-mode-e2e in your project.

What does Device Mode E2E need to run?

Going by SKILL.md and its folder, Device Mode E2E needs JavaScript for the scripts in its folder, the command-line tools its instructions call (node, npm, nvm, npx, git and curl) and credentials named WRITE_KEY. Our summary lists: Node.js; A credential in WRITE_KEY.

Does Device Mode E2E access the network?

SKILL.md names 4 domains. In commands or code: cdn.staging.rudderlabs.com, cdn.rudderlabs.com, cdn.dev.rudderlabs.com and api.rudderstack.com; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.

Is Device Mode E2E safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Device Mode E2E use?

Device Mode E2E is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Device Mode E2E use?

About 6.3k tokens (SKILL.md is roughly 25k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Device Mode E2E?

Skills that share tags, products or a category with Device Mode E2E: E2E Test (crowdin/crowdin-api-client-js, 139 stars), Javascript Testing Expert (softmaple/softmaple, 159 stars), Acceptance Test Authoring (intent-driven-dev/intent-driven-template, 160 stars) and Python Testing Patterns (jh941213/my-cc-harness, 126 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Device Mode E2E?

rudderlabs (a GitHub organization) maintains it in rudderlabs/rudder-sdk-js, which has 178 GitHub stars. The repository was last updated on October 7, 2026.

Source: rudderlabs/rudder-sdk-js on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.