Skip to content

[codex] Add local Playwright workflow smoke coverage#24

Merged
jruszo merged 2 commits intomasterfrom
feature/playwright-e2e-smoke
Apr 9, 2026
Merged

[codex] Add local Playwright workflow smoke coverage#24
jruszo merged 2 commits intomasterfrom
feature/playwright-e2e-smoke

Conversation

@jruszo
Copy link
Copy Markdown
Owner

@jruszo jruszo commented Apr 9, 2026

What changed

This PR adds a local-first Playwright smoke suite for the seeded demo environment and wires in the supporting reset scripts, frontend test hooks, and backend/demo fixes needed to make the end-to-end flows deterministic.

It covers:

  • DDL submit -> approve -> execute
  • DML submit -> PM approve -> DBA approve -> execute
  • export submit -> approve -> execute -> download
  • DML coverage for both backup disabled and backup enabled

It also makes the local demo database containers ephemeral again so the seeded demo state is recreated from scratch for each destructive E2E reset.

Why it changed

We wanted browser-level confidence that the real user workflows work against the local demo stack, not just API-level checks.

While wiring the suite, the runs exposed two backend/demo issues:

  • workflow submission was forcing is_backup=true when the backup toggle was disabled
  • MySQL DML backup execution in the demo stack failed because the seeded MySQL user could not read master binlog status

Root cause and fixes

  • sql_api no longer overrides the submitted backup value when the backup switch is hidden.
  • The demo MySQL seed now grants REPLICATION CLIENT on *.* to demo_archery, which allows the backup/binlog path to execute in the local demo environment.
  • Export workflow permissions and local download behavior were tightened so the export smoke flow works reliably.
  • The Playwright suite now toggles enable_backup_switch during the run and asserts the saved is_backup flag through the workflow detail API for both backup-off and backup-on DML paths.

Impact

  • frontend/npm run e2e now performs a destructive Docker reset, reseeds the local demo stack, starts the frontend locally on port 5173, and runs the Playwright smoke suite.
  • The local demo environment gives stronger confidence that approvals, executions, and export downloads work through the real UI.
  • MySQL DML workflows in the demo environment now work in both backup and non-backup modes.

Validation

  • bash scripts/e2e/reset-local-env.sh
  • docker exec datamingle-app python manage.py smoke_local_demo
  • docker exec datamingle-app python manage.py test sql_api.tests.TestWorkflow.test_submit_workflow_does_not_force_backup_when_toggle_disabled sql_api.tests.TestWorkflow.test_submit_workflow_preserves_backup_when_toggle_enabled
  • docker exec datamingle-app python manage.py test sql_api.tests.TestWorkflow.test_submit_export_workflow sql_api.tests.TestWorkflow.test_sqlcheck_ignores_schema_name_for_engine_execute_check
  • black --check sql_api/serializers.py sql_api/tests.py
  • npm run build in frontend/
  • E2E_START_FRONTEND=1 bash ../scripts/e2e/run-local-playwright.sh in frontend/

Summary by CodeRabbit

Release Notes

  • New Features

    • SQL export workflow functionality for submitting data export requests with approval workflows.
  • Improvements

    • Enhanced form accessibility with improved label-to-input associations across workflow and login forms.
  • Chores

    • Added end-to-end testing infrastructure.
    • Updated project dependencies and build configuration.
    • Updated database permissions for export operations.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

This PR introduces comprehensive Playwright-based end-to-end testing infrastructure alongside targeted instrumentation updates. Changes include new test helper modules, shell orchestration scripts, a Playwright config, and a smoke test suite. Frontend components are updated with data-testid attributes for test targeting. Backend systems are extended with export workflow permissions, environment seeding updates, and minor API/storage refactoring. Docker Compose configuration transitions to ephemeral demo databases.

Changes

Cohort / File(s) Summary
Git Ignore Updates
.gitignore, frontend/.gitignore
Added patterns to ignore Playwright test artifacts (playwright-report/, test-results/) and Docker export download directories (DataExportFile/, dictionary/).
Frontend Package & Config
frontend/package.json, frontend/playwright.config.ts, frontend/README.md
Added npm scripts for E2E workflows (e2e, e2e:test, e2e:reset, e2e:install), installed @playwright/test dependency, created Playwright config with single-worker execution, 120s test timeout, HTML reporting, and http://127.0.0.1:5173 as baseURL.
Frontend Test Instrumentation
frontend/src/components/queries/SqlCodeEditor.vue, frontend/src/views/LoginView.vue, frontend/src/views/WorkflowCreateView.vue, frontend/src/views/ExportWorkflowCreateView.vue, frontend/src/views/WorkflowsView.vue
Added data-testid attributes and matching id/for attributes to form controls, buttons, and labels across login, workflow creation, export workflow, and workflow detail views for improved test targeting and accessibility.
E2E Test Infrastructure
frontend/tests/e2e/support/workflow-helpers.ts
New 187-line helper module providing Playwright utilities: role-based authentication (createRoleSession, loginAs), workflow UI interactions (fillSqlEditor, openWorkflowDetail, clickAndAcceptDialogIfPresent), status polling/waiting (waitForWorkflowStatus, waitForWorkflowAction), backend verification (expectWorkflowBackupFlag), and environment configuration (setBackupSwitchEnabled).
E2E Test Suite
frontend/tests/e2e/workflow-smoke.spec.ts
New 253-line Playwright smoke test covering four end-to-end workflows: single-stage DDL, multi-stage DML (with/without backup), and export workflow, each with role-based approval chains, execution verification, and assertion of review/execution rows.
E2E Orchestration Scripts
scripts/e2e/run-local-smoke.sh, scripts/e2e/reset-local-env.sh, scripts/e2e/run-local-playwright.sh
New shell scripts orchestrating E2E test execution: smoke runner chains reset and playwright runners, reset script manages Docker stack teardown/rebuild and smoke_local_demo seeding with polling/retry logic, playwright runner starts Vite frontend and verifies login page availability before running tests.
Backend Permission & Seeding Updates
sql/local_demo.py, sql/test_local_demo.py, sql/management/commands/smoke_local_demo.py
Added export workflow permissions (menu_sqlexportworkflow, sqlexport_submit, offline_download) to demo user groups, extended smoke test assertions to validate export submission metadata endpoint and DBA download permission.
Storage & API Refactoring
sql/offlinedownload.py, sql_api/api_workflow.py, sql_api/serializers.py
Simplified download_export_file to derive storage_type from DynamicStorage instance; removed optional schema_name parameter from ExecuteCheck.post engine call; removed config-based is_backup override logic, deferring to submitted workflow value.
Backend Test Expansion
sql_api/tests.py, sql/test_offlinedownload.py
Added test coverage for schema_name omission in check API, backup toggle behavior across enable/disable states, and download_export_file with mocked storage returning HTTP 200 with proper attachment headers.
Docker & Documentation Updates
src/docker-compose/docker-compose.local-arm.yml, src/docker-compose/LOCAL_DEMO.md, src/docker-compose/mysql-demo/init/001-demo-schema.sql
Removed persistent volumes from demo MySQL/PostgreSQL services; updated reset documentation to use ephemeral stack teardown/rebuild; added REPLICATION CLIENT privilege grant for demo_archery user.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Whiskers twitch with testing glee,
Playwright hops through every tree,
Data-testids bloom and shine,
E2E smoke workflows align,
Workflows dance from test to test!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% 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 PR title clearly and specifically describes the main change: adding Playwright-based end-to-end workflow smoke test coverage for the local/demo environment.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/playwright-e2e-smoke

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.

@jruszo jruszo marked this pull request as ready for review April 9, 2026 12:33
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (5)
sql_api/tests.py (1)

3224-3309: Tighten backup-toggle assertions to validate config effect (not just payload echo).

Both tests currently assert that persisted is_backup matches request input, so they still pass even if enable_backup_switch is ignored. Please add one explicit assertion case that distinguishes toggle behavior (or rename tests to reflect payload passthrough semantics).

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

In `@sql_api/tests.py` around lines 3224 - 3309, The tests currently only echo the
request payload so they don't prove the enable_backup_switch is applied; change
test_submit_workflow_does_not_force_backup_when_toggle_disabled to send a
payload with "is_backup": True while SysConfig.set("enable_backup_switch",
False) and assert the persisted SqlWorkflow.is_backup is False (verify via
SqlWorkflow.objects.get(id=workflow_id).is_backup), and keep
test_submit_workflow_preserves_backup_when_toggle_enabled sending "is_backup":
True with SysConfig.set("enable_backup_switch", True) and asserting the
persisted SqlWorkflow.is_backup is True; reference the test methods
test_submit_workflow_does_not_force_backup_when_toggle_disabled,
test_submit_workflow_preserves_backup_when_toggle_enabled, SysConfig,
SqlWorkflow, and response_data to locate and update the assertions and request
payload.
frontend/src/views/WorkflowCreateView.vue (1)

745-752: Give the backup checkbox an explicit accessible name.

This control has an id, but no associated <label for> or aria-label, which weakens screen-reader usability.

Suggested minimal fix
-                  <input
+                  <input
                     id="workflow-backup-toggle"
                     v-model="form.isBackup"
                     data-testid="workflow-backup-toggle"
+                    aria-label="Backup SQL"
                     class="h-4 w-4 rounded border-slate-300"
                     type="checkbox"
                     :disabled="submitting"
                   />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/views/WorkflowCreateView.vue` around lines 745 - 752, The backup
checkbox input with id "workflow-backup-toggle" lacks an accessible name; add an
explicit label or aria-label tied to that input so screen readers can announce
it. Update the template in WorkflowCreateView.vue to either add a <label
for="workflow-backup-toggle">Backup workflow</label> adjacent to the input or
add an aria-label (e.g., aria-label="Enable workflow backup") on the input
element bound to form.isBackup; ensure the label text or aria-label clearly
describes the control and that the :disabled="submitting" behavior is preserved.
sql/management/commands/smoke_local_demo.py (1)

97-100: Consider asserting expected demo instance names, not only non-empty export scope.

A >= 1 check can pass even if one or more seeded demo instances silently drop from export visibility.

Proposed tightening
-        if len(export_metadata.get("instances", [])) < 1:
+        export_instances = {
+            item.get("instance_name")
+            for item in export_metadata.get("instances", [])
+            if item.get("instance_name")
+        }
+        expected_export_instances = {
+            "demo-mysql-workflow",
+            "demo-pgsql-workflow",
+        }
+        if not expected_export_instances.issubset(export_instances):
             raise CommandError(
-                "Export submission metadata is missing readable demo instances."
+                "Export submission metadata is missing expected demo instances."
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sql/management/commands/smoke_local_demo.py` around lines 97 - 100, The
current check in smoke_local_demo (where export_metadata.get("instances", []) is
validated) only ensures non-empty export scope; update it to verify that the
exported instance names match the expected demo instances by name. In the
command handler (smoke_local_demo) compute the set of expected demo instance
names (e.g., expected_instances = {"demo1", "demo2", ...} or derive from
DEMO_SEED or similar) and compare against {inst["name"] for inst in
export_metadata.get("instances", [])}; if any expected names are missing, raise
CommandError listing the missing instance names so missing seeded demos fail
loudly instead of silently passing the >=1 check.
frontend/tests/e2e/support/workflow-helpers.ts (1)

148-165: Synchronous Docker exec blocks the Node event loop.

execFileSync blocks the Node process while the Docker command runs. For a short-lived configuration command this is acceptable, but if the container is slow to respond or the command times out, it could cause test hangs without clear error reporting.

Consider adding a timeout option to execFileSync to bound the blocking duration.

♻️ Optional: Add timeout to prevent indefinite blocking
 export function setBackupSwitchEnabled(enabled: boolean) {
   execFileSync(
     'docker',
     [
       'exec',
       'datamingle-app',
       'python',
       'manage.py',
       'shell',
       '-c',
       `from common.config import SysConfig; SysConfig().set("enable_backup_switch", ${enabled ? 'True' : 'False'})`,
     ],
     {
       cwd: REPO_ROOT,
       stdio: 'pipe',
+      timeout: 30_000,
     },
   )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/tests/e2e/support/workflow-helpers.ts` around lines 148 - 165, The
setBackupSwitchEnabled function uses execFileSync which can block the Node event
loop indefinitely if Docker is slow; add a timeout option to the execFileSync
call (e.g., via the options object passed to execFileSync in
setBackupSwitchEnabled) to bound how long the sync call can block and surface a
clear error if the command exceeds the timeout; optionally consider switching to
the async execFile or spawn APIs for non-blocking behavior, but at minimum add a
reasonable timeout value and handle the thrown error from execFileSync in
setBackupSwitchEnabled to improve test reliability and error reporting.
scripts/e2e/run-local-playwright.sh (1)

33-36: Consider extracting the verification script for readability.

The inline Node script on line 35 is functional but difficult to read and maintain. Consider extracting it to a separate file or at least using a heredoc for better formatting.

♻️ Optional: Use heredoc for readability
 verify_frontend() {
   cd "$FRONTEND_DIR"
-  node -e "const { chromium } = require('@playwright/test'); (async () => { const browser = await chromium.launch(); const page = await browser.newPage(); await page.goto('${FRONTEND_URL}/login', { waitUntil: 'networkidle' }); const count = await page.locator('[data-testid=login-username]').count(); await browser.close(); if (count === 0) { process.exit(1); } })().catch(async (error) => { console.error(error); process.exit(1); });"
+  node <<EOF
+const { chromium } = require('@playwright/test');
+(async () => {
+  const browser = await chromium.launch();
+  const page = await browser.newPage();
+  await page.goto('${FRONTEND_URL}/login', { waitUntil: 'networkidle' });
+  const count = await page.locator('[data-testid=login-username]').count();
+  await browser.close();
+  if (count === 0) process.exit(1);
+})().catch((error) => {
+  console.error(error);
+  process.exit(1);
+});
+EOF
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/e2e/run-local-playwright.sh` around lines 33 - 36, The inline Node
script inside verify_frontend is hard to read; extract it into a standalone
script (e.g., run_headless_verify.js) or emit it via a heredoc and then invoke
node from verify_frontend so the logic is readable and maintainable; move the
Playwright logic that imports chromium, launches the browser, navigates to
"${FRONTEND_URL}/login", checks locator '[data-testid=login-username]' count,
exits nonzero on failure, and prints errors into that file, then change
verify_frontend to cd "$FRONTEND_DIR" and run node against the extracted script
(keeping use of FRONTEND_DIR, FRONTEND_URL, verify_frontend and the
'[data-testid=login-username]' locator).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/src/views/WorkflowCreateView.vue`:
- Around line 745-752: The backup checkbox input with id
"workflow-backup-toggle" lacks an accessible name; add an explicit label or
aria-label tied to that input so screen readers can announce it. Update the
template in WorkflowCreateView.vue to either add a <label
for="workflow-backup-toggle">Backup workflow</label> adjacent to the input or
add an aria-label (e.g., aria-label="Enable workflow backup") on the input
element bound to form.isBackup; ensure the label text or aria-label clearly
describes the control and that the :disabled="submitting" behavior is preserved.

In `@frontend/tests/e2e/support/workflow-helpers.ts`:
- Around line 148-165: The setBackupSwitchEnabled function uses execFileSync
which can block the Node event loop indefinitely if Docker is slow; add a
timeout option to the execFileSync call (e.g., via the options object passed to
execFileSync in setBackupSwitchEnabled) to bound how long the sync call can
block and surface a clear error if the command exceeds the timeout; optionally
consider switching to the async execFile or spawn APIs for non-blocking
behavior, but at minimum add a reasonable timeout value and handle the thrown
error from execFileSync in setBackupSwitchEnabled to improve test reliability
and error reporting.

In `@scripts/e2e/run-local-playwright.sh`:
- Around line 33-36: The inline Node script inside verify_frontend is hard to
read; extract it into a standalone script (e.g., run_headless_verify.js) or emit
it via a heredoc and then invoke node from verify_frontend so the logic is
readable and maintainable; move the Playwright logic that imports chromium,
launches the browser, navigates to "${FRONTEND_URL}/login", checks locator
'[data-testid=login-username]' count, exits nonzero on failure, and prints
errors into that file, then change verify_frontend to cd "$FRONTEND_DIR" and run
node against the extracted script (keeping use of FRONTEND_DIR, FRONTEND_URL,
verify_frontend and the '[data-testid=login-username]' locator).

In `@sql_api/tests.py`:
- Around line 3224-3309: The tests currently only echo the request payload so
they don't prove the enable_backup_switch is applied; change
test_submit_workflow_does_not_force_backup_when_toggle_disabled to send a
payload with "is_backup": True while SysConfig.set("enable_backup_switch",
False) and assert the persisted SqlWorkflow.is_backup is False (verify via
SqlWorkflow.objects.get(id=workflow_id).is_backup), and keep
test_submit_workflow_preserves_backup_when_toggle_enabled sending "is_backup":
True with SysConfig.set("enable_backup_switch", True) and asserting the
persisted SqlWorkflow.is_backup is True; reference the test methods
test_submit_workflow_does_not_force_backup_when_toggle_disabled,
test_submit_workflow_preserves_backup_when_toggle_enabled, SysConfig,
SqlWorkflow, and response_data to locate and update the assertions and request
payload.

In `@sql/management/commands/smoke_local_demo.py`:
- Around line 97-100: The current check in smoke_local_demo (where
export_metadata.get("instances", []) is validated) only ensures non-empty export
scope; update it to verify that the exported instance names match the expected
demo instances by name. In the command handler (smoke_local_demo) compute the
set of expected demo instance names (e.g., expected_instances = {"demo1",
"demo2", ...} or derive from DEMO_SEED or similar) and compare against
{inst["name"] for inst in export_metadata.get("instances", [])}; if any expected
names are missing, raise CommandError listing the missing instance names so
missing seeded demos fail loudly instead of silently passing the >=1 check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4c97362-ab8d-4a19-bd51-d362d8b1635f

📥 Commits

Reviewing files that changed from the base of the PR and between c233e86 and 5e13947.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (26)
  • .gitignore
  • frontend/.gitignore
  • frontend/README.md
  • frontend/package.json
  • frontend/playwright.config.ts
  • frontend/src/components/queries/SqlCodeEditor.vue
  • frontend/src/views/ExportWorkflowCreateView.vue
  • frontend/src/views/LoginView.vue
  • frontend/src/views/WorkflowCreateView.vue
  • frontend/src/views/WorkflowsView.vue
  • frontend/tests/e2e/support/workflow-helpers.ts
  • frontend/tests/e2e/workflow-smoke.spec.ts
  • scripts/e2e/reset-local-env.sh
  • scripts/e2e/run-local-playwright.sh
  • scripts/e2e/run-local-smoke.sh
  • sql/local_demo.py
  • sql/management/commands/smoke_local_demo.py
  • sql/offlinedownload.py
  • sql/test_local_demo.py
  • sql/test_offlinedownload.py
  • sql_api/api_workflow.py
  • sql_api/serializers.py
  • sql_api/tests.py
  • src/docker-compose/LOCAL_DEMO.md
  • src/docker-compose/docker-compose.local-arm.yml
  • src/docker-compose/mysql-demo/init/001-demo-schema.sql
💤 Files with no reviewable changes (2)
  • sql_api/serializers.py
  • src/docker-compose/docker-compose.local-arm.yml

@jruszo jruszo merged commit d734ff4 into master Apr 9, 2026
7 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 28, 2026
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