Skip to content

test(e2e): add distributed stack Playwright harness#1996

Merged
yottahmd merged 19 commits intomainfrom
test/e2e-foundation
Apr 12, 2026
Merged

test(e2e): add distributed stack Playwright harness#1996
yottahmd merged 19 commits intomainfrom
test/e2e-foundation

Conversation

@yottahmd
Copy link
Copy Markdown
Collaborator

@yottahmd yottahmd commented Apr 12, 2026

Summary

  • add a self-contained Playwright E2E harness for a shared-nothing distributed Dagu stack, including bin-e2e, make test-e2e, and stack bootstrap under scripts/e2e/
  • cover distributed queue execution, DAG-run stop/retry/reschedule flows, auth and permissions, remote nodes, scheduler recovery, worker failover, and local zombie recovery with shared helpers and fixtures
  • run the suite in CI with a dedicated Playwright workflow and keep local and CI runs aligned by auto-generating a dev license and deriving the base URL and ports from the E2E stack config

Test plan

  • make test-e2e
  • cd ui && pnpm typecheck
  • cd ui && pnpm playwright test e2e/dag-run-actions.spec.ts e2e/scheduler-recovery.spec.ts e2e/worker-failover.spec.ts e2e/zombie-recovery.spec.ts --project chromium

Summary by CodeRabbit

  • Tests

    • Added a comprehensive Playwright E2E suite covering auth flows, permissions, DAG CRUD/actions, distributed stack, worker failover, scheduler recovery, zombie recovery, remote nodes, and helpers/fixtures.
  • Chores

    • Introduced E2E CI workflow, Makefile E2E targets, Playwright config, npm scripts, and Playwright artifacts handling.
    • Added local E2E tooling: stack bootstrap script and dev license generator; updated .gitignore for Playwright reports.
  • UI

    • Unified permission checks to use the centralized auth context for DAG creation/editing controls.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02b0088a-31e6-4430-ae43-5403036f8568

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Playwright-based end-to-end testing: build/test Makefile targets, Playwright config and scripts, a local distributed E2E stack runner, E2E helpers/fixtures, many Playwright test suites, and minor license/build-related code adjustments.

Changes

Cohort / File(s) Summary
Build & CI
/.gitignore, Makefile, .github/workflows/e2e-ci.yaml, ui/package.json
Ignore Playwright report dir; add bin-e2e and test-e2e Make targets; add Playwright scripts/devDependency; add GitHub Actions workflow to run E2E CI.
E2E orchestration scripts
scripts/e2e/generate-dev-license.mjs, scripts/e2e/start-stack.sh
New Node script to emit Ed25519-signed dev license tokens; new Bash script to provision and manage a local distributed test stack (service configs, lifecycle, health checks, PID/log management, license injection).
Playwright config & harness
ui/playwright.config.ts, ui/playwright-report/ (ignored)
New Playwright config resolving baseURL/ports from env, webServer invoking start-stack.sh, CI-friendly settings, and artifact/reporting configuration.
E2E helpers & fixtures
ui/e2e/helpers/e2e.ts, ui/e2e/fixtures/dags/e2e-distributed-queue.yaml
Large typed helper module for stack loading, DAG write/start/enqueue, auth (UI/API), worker/queue polling, step log access, and service control; added distributed-queue DAG fixture.
Playwright test suites
ui/e2e/*.spec.ts
(auth-flows.spec.ts, auth-permissions.spec.ts, dag-crud.spec.ts, dag-run-actions.spec.ts, distributed-stack.spec.ts, remote-nodes.spec.ts, scheduler-recovery.spec.ts, worker-failover.spec.ts, zombie-recovery.spec.ts)
Many new end-to-end tests covering auth flows/permissions, DAG CRUD, run actions (stop/retry/reschedule), distributed queue behavior, remote nodes, scheduler recovery, worker failover, and zombie-run recovery.
UI auth/permission refactor
ui/src/features/.../CreateDAGButton.tsx, CreateDAGModal.tsx, DAGEditButtons.tsx, DAGSpec.tsx
Replaced config-based write-permission checks with useCanWrite() (AuthContext) across DAG creation/edit UI components.
License build tag changes
internal/license/pubkey.go, internal/license/pubkey_dev.go
Removed dev-only pubkey implementation and its build tag; pubkey.go build constraint altered (dev-specific file deleted).
Tests / minor test tweak
internal/service/telegram/bot_test.go
Adjusted test predicate to observe bot API send count instead of internal service append state.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Playwright as E2E Test (Playwright)
participant Starter as start-stack.sh
participant DaguBin as dagu E2E binary
participant Coordinator as Coordinator
participant Server as Server
participant Worker as Worker(s)
participant Scheduler as Scheduler
Playwright->>Starter: invoke webServer (start-stack.sh)
Starter->>DaguBin: launch Coordinator, Server, Worker(s), Scheduler, Remote
DaguBin->>Coordinator: start & expose health
DaguBin->>Server: start & expose API
DaguBin->>Worker: register with Server
Starter->>Playwright: wait for health endpoints / registration
Playwright->>Server: API auth/login, DAG operations
Playwright->>Worker: (indirect) trigger runs via Server
Playwright->>Starter: request stop/restart services (stop-service/start-service)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: adding a Playwright end-to-end testing harness for a distributed stack, covering test infrastructure, helpers, fixtures, CI workflow, and related configuration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/e2e-foundation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (12)
Makefile (1)

362-364: Reuse cp-assets here to avoid copy-step drift.

test-e2e currently duplicates asset copy logic already defined in cp-assets.

♻️ Proposed refactor
-	`@rm` -f ${FE_ASSETS_DIR}/*
-	`@cp` ${FE_BUILD_DIR}/* ${FE_ASSETS_DIR}
+	@$(MAKE) cp-assets
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` around lines 362 - 364, The test-e2e target duplicates the asset
copy steps; remove the explicit "@rm -f ${FE_ASSETS_DIR}/*" and "@cp
${FE_BUILD_DIR}/* ${FE_ASSETS_DIR}" lines and instead invoke the existing
cp-assets target (e.g., call "$(MAKE) cp-assets" or add cp-assets as a
prerequisite for the test-e2e recipe) so asset copying is centralized in the
cp-assets target.
ui/e2e/auth-permissions.spec.ts (2)

73-79: Add remoteNode query parameter to API call.

Per coding guidelines, all API calls must include the remoteNode parameter to route requests to the correct node in multi-node deployments.

♻️ Proposed fix
-  const startResponse = await request.post(`/api/v1/dags/${encodeURIComponent(fileName)}/start`, {
+  const startResponse = await request.post(`/api/v1/dags/${encodeURIComponent(fileName)}/start?remoteNode=local`, {
     headers: {
       Authorization: `Bearer ${viewerToken}`,
       'Content-Type': 'application/json',
     },
     data: {},
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/auth-permissions.spec.ts` around lines 73 - 79, The POST call that
sets startResponse (request.post in the test) is missing the required remoteNode
query parameter; update the request URL used in the request.post for
`/api/v1/dags/${encodeURIComponent(fileName)}/start` to include
`?remoteNode=<nodeId>` (or interpolate the test's existing remote node variable)
so the call routes correctly, and keep the existing headers/authorization
(Authorization: `Bearer ${viewerToken}`) and data payload unchanged.

1-11: Missing GPL v3 license header.

Per coding guidelines, source files must have GPL v3 license headers managed via make addlicense.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/auth-permissions.spec.ts` around lines 1 - 11, The file
auth-permissions.spec.ts is missing the GPL v3 license header; run the
repository license tool (make addlicense) or insert the GPL v3 header at the top
of the file so the header precedes the imports (ensure it appears before the
import lines referencing helpers like createUser, loginViaAPI, loginViaUI,
uniqueName, etc.), then commit the updated file so the test uses the standard
license header.
ui/e2e/scheduler-recovery.spec.ts (1)

1-11: Missing GPL v3 license header.

Per coding guidelines, source files must have GPL v3 license headers managed via make addlicense. This file is missing the required header.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/scheduler-recovery.spec.ts` around lines 1 - 11, This file is missing
the required GPL v3 license header; run the repository tool to add it (execute
`make addlicense`) or insert the standard GPL v3 header at the top of the file
so it precedes the imports (ensure it appears above the existing symbols like
listDAGRuns, loadStack, loginViaAPI, startService, stopService, uniqueName,
waitForRunStatus, writeLocalDAG), then commit the updated file.
ui/e2e/helpers/e2e.ts (4)

1-4: Missing GPL v3 license header.

Per coding guidelines, source files must have GPL v3 license headers managed via make addlicense.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/helpers/e2e.ts` around lines 1 - 4, This file is missing the GPL v3
license header; add the standard GPL v3 header comment at the very top of the
file (above the existing import lines such as "import { expect... }" and other
imports) by running the repository license tool (make addlicense) or inserting
the canonical GPL v3 header, then stage/commit the change so the file includes
the required license header.

223-238: Add remoteNode query parameter to startDAG helper.

Per coding guidelines, all API calls must include the remoteNode parameter. This helper is missing it.

♻️ Proposed fix
 export async function startDAG(
   request: APIRequestContext,
   token: string,
   fileName: string,
-  body: Record<string, unknown> = {}
+  body: Record<string, unknown> = {},
+  remoteNode: string = 'local'
 ): Promise<string> {
-  const response = await request.post(`/api/v1/dags/${encodeURIComponent(fileName)}/start`, {
+  const response = await request.post(`/api/v1/dags/${encodeURIComponent(fileName)}/start?remoteNode=${encodeURIComponent(remoteNode)}`, {
     headers: authHeaders(token),
     data: body,
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/helpers/e2e.ts` around lines 223 - 238, The startDAG helper is missing
the required remoteNode query parameter; modify the startDAG function signature
to accept a remoteNode: string parameter (e.g., startDAG(request, token,
fileName, remoteNode, body = {})) and append it to the request URL as a query
parameter (use encodeURIComponent on remoteNode) when calling request.post for
/api/v1/dags/${encodeURIComponent(fileName)}/start; update any callers to pass
the appropriate remoteNode value.

240-255: Add remoteNode query parameter to enqueueDAG helper.

Per coding guidelines, all API calls must include the remoteNode parameter. This helper is missing it.

♻️ Proposed fix
 export async function enqueueDAG(
   request: APIRequestContext,
   token: string,
   fileName: string,
-  body: Record<string, unknown> = {}
+  body: Record<string, unknown> = {},
+  remoteNode: string = 'local'
 ): Promise<string> {
-  const response = await request.post(`/api/v1/dags/${encodeURIComponent(fileName)}/enqueue`, {
+  const response = await request.post(`/api/v1/dags/${encodeURIComponent(fileName)}/enqueue?remoteNode=${encodeURIComponent(remoteNode)}`, {
     headers: authHeaders(token),
     data: body,
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/helpers/e2e.ts` around lines 240 - 255, The enqueueDAG helper is
missing the required remoteNode query parameter; update the enqueueDAG function
to accept a remoteNode string argument (or optional with default) and append
?remoteNode=${encodeURIComponent(remoteNode)} to the POST URL (i.e., call
request.post(`/api/v1/dags/${encodeURIComponent(fileName)}/enqueue?remoteNode=${encodeURIComponent(remoteNode)}`)
), leaving the authHeaders and body handling the same and returning
payload.dagRunId as before; ensure any callers/tests are updated to pass the
remoteNode value.

485-493: killProcess may throw if process is already dead.

The execFileSync call will throw an error if the process doesn't exist or is already terminated. Consider handling this case gracefully.

🛡️ Proposed fix to handle already-dead processes
 export async function killProcess(
   pid: number | string,
   signal: 'TERM' | 'KILL' = 'KILL'
 ): Promise<void> {
-  execFileSync('kill', [`-${signal}`, `${pid}`], {
-    cwd: repoRoot,
-    stdio: 'pipe',
-  });
+  try {
+    execFileSync('kill', [`-${signal}`, `${pid}`], {
+      cwd: repoRoot,
+      stdio: 'pipe',
+    });
+  } catch {
+    // Process may already be dead, which is acceptable
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/helpers/e2e.ts` around lines 485 - 493, Wrap the execFileSync call in
killProcess with a try/catch and swallow only the error that indicates the
target process is already gone; detect that by checking the thrown error's exit
code/message (e.g., exit status 1 or stderr containing "No such process"/ESRCH)
and return silently, but rethrow any other errors so genuine failures
(permissions, unexpected failures) are not masked; keep the function signature
and use existing symbols killProcess, execFileSync, repoRoot and signal to
locate the change.
ui/e2e/dag-run-actions.spec.ts (1)

1-14: Missing GPL v3 license header.

Per coding guidelines, source files must have GPL v3 license headers managed via make addlicense.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/dag-run-actions.spec.ts` around lines 1 - 14, This file is missing the
GPL v3 license header; locate the top of the file (just above the first import,
e.g. the line "import { expect, test } from '@playwright/test';") and add the
standard GPL v3 header managed by the repository tooling by running the project
helper to inject headers: run "make addlicense" (or apply the same header text
if you must add manually), then commit the updated file so the license header
appears before the imports.
ui/e2e/distributed-stack.spec.ts (2)

40-44: Consider escaping regex special characters in queue name.

The static analysis tool flagged potential ReDoS risk when constructing a RegExp from variable input. While the queue name comes from controlled test configuration, escaping special regex characters would be safer practice.

🛡️ Proposed fix to escape regex special characters
+function escapeRegExp(str: string): string {
+  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+}
+
 const sharedQueueCard = page.getByRole('link', {
-  name: new RegExp(stack.queues.shared, 'i'),
+  name: new RegExp(escapeRegExp(stack.queues.shared), 'i'),
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/distributed-stack.spec.ts` around lines 40 - 44, The test constructs a
RegExp from variable input (stack.queues.shared) which can introduce ReDoS or
unintended regex behavior; update the page.getByRole call to use an escaped
regex by first escaping regex metacharacters from stack.queues.shared (add or
reuse a helper like escapeRegExp) and then build new RegExp(escapedName, 'i'),
or alternatively pass the name as a plain string/match option instead of a
RegExp; update references in distributed-stack.spec.ts around sharedQueueCard
and the page.getByRole invocation to use the escaped value.

1-12: Missing GPL v3 license header.

Per coding guidelines, source files must have GPL v3 license headers managed via make addlicense.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/distributed-stack.spec.ts` around lines 1 - 12, The file
distributed-stack.spec.ts is missing the required GPL v3 license header; add the
standard GPL v3 header used across the repo to the very top of the file (above
the import statements that include expect and test), and then run the repository
license tool to apply/verify it by executing make addlicense so the header is
managed consistently with other files.
ui/e2e/worker-failover.spec.ts (1)

1-16: Missing GPL v3 license header.

Per coding guidelines, source files must have GPL v3 license headers managed via make addlicense.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/worker-failover.spec.ts` around lines 1 - 16, The file is missing the
GPL v3 license header; add the standard GPL v3 comment block at the very top of
the file (above the first import line such as "import { expect, test } from
'@playwright/test'") to match repo style, preferably by running the repository
tool to apply headers (run make addlicense) or by pasting the exact project GPL
v3 header manually so it matches other test files.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/e2e/remote-nodes.spec.ts`:
- Line 1: This file remote-nodes.spec.ts is missing the required GPL v3 license
header; add the repository's standard GPL v3 comment block at the very top of
remote-nodes.spec.ts (matching the format used in other .ts/.tsx/.js files) or
run the repo tool to apply it by executing make addlicense so the correct header
is inserted consistently across the codebase.
- Around line 47-54: The expect.poll call that checks the remote DAG readiness
lacks an explicit timeout; update the expect.poll invocation in
remote-nodes.spec.ts (the block using expect.poll(async () => { const response =
await
request.get(`${stack.remote.apiBaseURL}/dags/${encodeURIComponent(remoteFileName)}`);
return response.ok(); })) to pass an options object with timeout: 15000
(matching the local DAG wait) so the poll will wait up to 15 seconds for
readiness in slower CI environments.

In `@ui/e2e/zombie-recovery.spec.ts`:
- Line 1: This file is missing the required GPL v3 license header; add the
standard GPL v3 header comment block at the top of
ui/e2e/zombie-recovery.spec.ts (above the import line) as used across the repo
and then run the repository's license tooling (e.g., make addlicense) to ensure
formatting/metadata matches other TypeScript files; reference this change for
the new test in zombie-recovery.spec.ts so the header appears before any code
(imports or tests).

In `@ui/playwright.config.ts`:
- Line 1: This file is missing the required GPL v3 license header; add the
standard GPL v3 header comment block to the top of ui/playwright.config.ts above
the existing import statement (the line containing "import { defineConfig,
devices } from '@playwright/test';"), then run the repository license tool
(e.g., make addlicense) to ensure the header is applied consistently to this and
other .ts/.tsx/.js/.go files.

---

Nitpick comments:
In `@Makefile`:
- Around line 362-364: The test-e2e target duplicates the asset copy steps;
remove the explicit "@rm -f ${FE_ASSETS_DIR}/*" and "@cp ${FE_BUILD_DIR}/*
${FE_ASSETS_DIR}" lines and instead invoke the existing cp-assets target (e.g.,
call "$(MAKE) cp-assets" or add cp-assets as a prerequisite for the test-e2e
recipe) so asset copying is centralized in the cp-assets target.

In `@ui/e2e/auth-permissions.spec.ts`:
- Around line 73-79: The POST call that sets startResponse (request.post in the
test) is missing the required remoteNode query parameter; update the request URL
used in the request.post for
`/api/v1/dags/${encodeURIComponent(fileName)}/start` to include
`?remoteNode=<nodeId>` (or interpolate the test's existing remote node variable)
so the call routes correctly, and keep the existing headers/authorization
(Authorization: `Bearer ${viewerToken}`) and data payload unchanged.
- Around line 1-11: The file auth-permissions.spec.ts is missing the GPL v3
license header; run the repository license tool (make addlicense) or insert the
GPL v3 header at the top of the file so the header precedes the imports (ensure
it appears before the import lines referencing helpers like createUser,
loginViaAPI, loginViaUI, uniqueName, etc.), then commit the updated file so the
test uses the standard license header.

In `@ui/e2e/dag-run-actions.spec.ts`:
- Around line 1-14: This file is missing the GPL v3 license header; locate the
top of the file (just above the first import, e.g. the line "import { expect,
test } from '@playwright/test';") and add the standard GPL v3 header managed by
the repository tooling by running the project helper to inject headers: run
"make addlicense" (or apply the same header text if you must add manually), then
commit the updated file so the license header appears before the imports.

In `@ui/e2e/distributed-stack.spec.ts`:
- Around line 40-44: The test constructs a RegExp from variable input
(stack.queues.shared) which can introduce ReDoS or unintended regex behavior;
update the page.getByRole call to use an escaped regex by first escaping regex
metacharacters from stack.queues.shared (add or reuse a helper like
escapeRegExp) and then build new RegExp(escapedName, 'i'), or alternatively pass
the name as a plain string/match option instead of a RegExp; update references
in distributed-stack.spec.ts around sharedQueueCard and the page.getByRole
invocation to use the escaped value.
- Around line 1-12: The file distributed-stack.spec.ts is missing the required
GPL v3 license header; add the standard GPL v3 header used across the repo to
the very top of the file (above the import statements that include expect and
test), and then run the repository license tool to apply/verify it by executing
make addlicense so the header is managed consistently with other files.

In `@ui/e2e/helpers/e2e.ts`:
- Around line 1-4: This file is missing the GPL v3 license header; add the
standard GPL v3 header comment at the very top of the file (above the existing
import lines such as "import { expect... }" and other imports) by running the
repository license tool (make addlicense) or inserting the canonical GPL v3
header, then stage/commit the change so the file includes the required license
header.
- Around line 223-238: The startDAG helper is missing the required remoteNode
query parameter; modify the startDAG function signature to accept a remoteNode:
string parameter (e.g., startDAG(request, token, fileName, remoteNode, body =
{})) and append it to the request URL as a query parameter (use
encodeURIComponent on remoteNode) when calling request.post for
/api/v1/dags/${encodeURIComponent(fileName)}/start; update any callers to pass
the appropriate remoteNode value.
- Around line 240-255: The enqueueDAG helper is missing the required remoteNode
query parameter; update the enqueueDAG function to accept a remoteNode string
argument (or optional with default) and append
?remoteNode=${encodeURIComponent(remoteNode)} to the POST URL (i.e., call
request.post(`/api/v1/dags/${encodeURIComponent(fileName)}/enqueue?remoteNode=${encodeURIComponent(remoteNode)}`)
), leaving the authHeaders and body handling the same and returning
payload.dagRunId as before; ensure any callers/tests are updated to pass the
remoteNode value.
- Around line 485-493: Wrap the execFileSync call in killProcess with a
try/catch and swallow only the error that indicates the target process is
already gone; detect that by checking the thrown error's exit code/message
(e.g., exit status 1 or stderr containing "No such process"/ESRCH) and return
silently, but rethrow any other errors so genuine failures (permissions,
unexpected failures) are not masked; keep the function signature and use
existing symbols killProcess, execFileSync, repoRoot and signal to locate the
change.

In `@ui/e2e/scheduler-recovery.spec.ts`:
- Around line 1-11: This file is missing the required GPL v3 license header; run
the repository tool to add it (execute `make addlicense`) or insert the standard
GPL v3 header at the top of the file so it precedes the imports (ensure it
appears above the existing symbols like listDAGRuns, loadStack, loginViaAPI,
startService, stopService, uniqueName, waitForRunStatus, writeLocalDAG), then
commit the updated file.

In `@ui/e2e/worker-failover.spec.ts`:
- Around line 1-16: The file is missing the GPL v3 license header; add the
standard GPL v3 comment block at the very top of the file (above the first
import line such as "import { expect, test } from '@playwright/test'") to match
repo style, preferably by running the repository tool to apply headers (run make
addlicense) or by pasting the exact project GPL v3 header manually so it matches
other test files.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e34b6dad-6a7b-4f19-a69c-4c616c7d819f

📥 Commits

Reviewing files that changed from the base of the PR and between d48a0b7 and d476638.

⛔ Files ignored due to path filters (1)
  • ui/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • .gitignore
  • Makefile
  • scripts/e2e/generate-dev-license.mjs
  • scripts/e2e/start-stack.sh
  • ui/e2e/auth-permissions.spec.ts
  • ui/e2e/dag-run-actions.spec.ts
  • ui/e2e/distributed-stack.spec.ts
  • ui/e2e/fixtures/dags/e2e-distributed-queue.yaml
  • ui/e2e/helpers/e2e.ts
  • ui/e2e/remote-nodes.spec.ts
  • ui/e2e/scheduler-recovery.spec.ts
  • ui/e2e/worker-failover.spec.ts
  • ui/e2e/zombie-recovery.spec.ts
  • ui/package.json
  • ui/playwright.config.ts

Comment thread ui/e2e/remote-nodes.spec.ts
Comment thread ui/e2e/remote-nodes.spec.ts
Comment thread ui/e2e/zombie-recovery.spec.ts
Comment thread ui/playwright.config.ts
@yottahmd yottahmd changed the title test(e2e): add distributed Playwright coverage test(e2e): add distributed stack Playwright harness Apr 12, 2026
Wait on final side-effect (sendCount) instead of intermediate
step (appendMessages) to avoid asserting before flush completes.
Cover create, rename, delete, suspend/resume, and invalid-name
validation for DAGs. Cover password change, admin password reset,
and manager/operator role permission boundaries.
Replace self-signed ephemeral keypair approach with CI secret-based
signing. Production binary now always uses hardcoded public key with
no env var or build tag escape hatch. E2e tests sign JWTs using the
real private key provided via DAGU_LICENSE_PRIVKEY_B64 secret.
Extract 32-byte seed from Go's 64-byte ed25519.PrivateKey format
and import via JWK instead of raw DER.
Node.js requires both d (seed) and x (public key) fields for
Ed25519 JWK private key import.
- pnpm/action-setup v5.0.0 → v6.0.0
- actions/upload-artifact v4.6.2 → v7.0.1
v6.0.0 breaks pnpm 10 lockfile parsing.
@yottahmd
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
ui/e2e/helpers/e2e.ts (2)

505-520: Unnecessary async keyword on synchronous function.

killProcess is marked async but contains no await expressions—it uses only synchronous execFileSync. The async can be removed.

♻️ Remove unnecessary async
-export async function killProcess(
+export function killProcess(
   pid: number | string,
   signal: 'TERM' | 'KILL' = 'KILL'
-): Promise<void> {
+): void {
   try {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/helpers/e2e.ts` around lines 505 - 520, The function killProcess is
marked async but contains no await and only uses synchronous execFileSync;
remove the unnecessary async modifier and change the signature from "export
async function killProcess(pid: number | string, signal: 'TERM' | 'KILL' =
'KILL'): Promise<void>" to a plain synchronous declaration (e.g., "export
function killProcess(...): void"), keeping the existing body and error handling
(isNoSuchProcessError, repoRoot, execFileSync) unchanged so callers get the
correct synchronous return type.

476-503: Consider removing unnecessary async keyword.

The stopService and startService functions are marked async but only use synchronous execFileSync calls. While this doesn't cause issues, it could be confusing.

♻️ Optional: Remove async from sync functions
-export async function stopService(
+export function stopService(
   serviceName: string,
   signal: 'TERM' | 'KILL' = 'TERM'
-): Promise<void> {
+): void {
   const stack = await loadStack();

Note: If you keep async, it's because loadStack() is async. The current implementation is actually correct since it awaits loadStack(). Disregard this suggestion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/e2e/helpers/e2e.ts` around lines 476 - 503, stopService and startService
are marked async but only appear to do synchronous work; verify whether
loadStack() remains asynchronous—if loadStack is synchronous remove the async
keyword and the await and call loadStack() directly in stopService/startService,
otherwise keep the async/await as-is; locate the functions stopService and
startService and adjust the async/await usage accordingly (or leave them
unchanged if loadStack() is async).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/e2e/generate-dev-license.mjs`:
- Around line 12-18: The code slices rawKey into seed and pub and constructs a
JWK without validating the decoded length; add a guard after decoding (rawKey,
used by rawKey.subarray, seed, pub and before createPrivateKey/privateKey) to
verify rawKey.length is at least 64 (or exactly 64 for strict validation) and
throw a clear, descriptive error if the environment value
(DAGU_LICENSE_PRIVKEY_B64) is malformed; return/exit early on failure so
createPrivateKey is never called with an invalid seed/pub pair.

In `@ui/e2e/scheduler-recovery.spec.ts`:
- Around line 30-51: The test uses a fixed 1_000ms sleep after writeLocalDAG
which causes flakiness; replace that sleep with a deterministic wait that polls
for the DAG to be indexed/ready (e.g., repeatedly call listDAGRuns(request,
token, dagName) or another DAG-ready API until it returns the expected
availability or a timeout), and only then call stopService('scheduler') and
proceed to the waitUntilDueMs/assertions; update the code around writeLocalDAG,
the current setTimeout call, and the point before stopService('scheduler') to
perform this polling with a sensible timeout and short interval.

---

Nitpick comments:
In `@ui/e2e/helpers/e2e.ts`:
- Around line 505-520: The function killProcess is marked async but contains no
await and only uses synchronous execFileSync; remove the unnecessary async
modifier and change the signature from "export async function killProcess(pid:
number | string, signal: 'TERM' | 'KILL' = 'KILL'): Promise<void>" to a plain
synchronous declaration (e.g., "export function killProcess(...): void"),
keeping the existing body and error handling (isNoSuchProcessError, repoRoot,
execFileSync) unchanged so callers get the correct synchronous return type.
- Around line 476-503: stopService and startService are marked async but only
appear to do synchronous work; verify whether loadStack() remains
asynchronous—if loadStack is synchronous remove the async keyword and the await
and call loadStack() directly in stopService/startService, otherwise keep the
async/await as-is; locate the functions stopService and startService and adjust
the async/await usage accordingly (or leave them unchanged if loadStack() is
async).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 74d980fd-3a0a-4319-97b0-978eaa4fed7b

📥 Commits

Reviewing files that changed from the base of the PR and between d48a0b7 and 172e7a5.

⛔ Files ignored due to path filters (1)
  • ui/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (25)
  • .github/workflows/e2e-ci.yaml
  • .gitignore
  • Makefile
  • internal/license/pubkey.go
  • internal/license/pubkey_dev.go
  • internal/service/telegram/bot_test.go
  • scripts/e2e/generate-dev-license.mjs
  • scripts/e2e/start-stack.sh
  • ui/e2e/auth-flows.spec.ts
  • ui/e2e/auth-permissions.spec.ts
  • ui/e2e/dag-crud.spec.ts
  • ui/e2e/dag-run-actions.spec.ts
  • ui/e2e/distributed-stack.spec.ts
  • ui/e2e/fixtures/dags/e2e-distributed-queue.yaml
  • ui/e2e/helpers/e2e.ts
  • ui/e2e/remote-nodes.spec.ts
  • ui/e2e/scheduler-recovery.spec.ts
  • ui/e2e/worker-failover.spec.ts
  • ui/e2e/zombie-recovery.spec.ts
  • ui/package.json
  • ui/playwright.config.ts
  • ui/src/features/dags/components/common/CreateDAGButton.tsx
  • ui/src/features/dags/components/common/CreateDAGModal.tsx
  • ui/src/features/dags/components/dag-editor/DAGEditButtons.tsx
  • ui/src/features/dags/components/dag-editor/DAGSpec.tsx
💤 Files with no reviewable changes (2)
  • internal/license/pubkey.go
  • internal/license/pubkey_dev.go

Comment thread scripts/e2e/generate-dev-license.mjs
Comment thread ui/e2e/scheduler-recovery.spec.ts
@yottahmd yottahmd merged commit 8d94016 into main Apr 12, 2026
6 checks passed
@yottahmd yottahmd deleted the test/e2e-foundation branch April 12, 2026 11:54
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