ruby_raider 3.8.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +18 -29
- data/.github/workflows/security.yml +68 -0
- data/.rubocop.yml +28 -0
- data/README.md +22 -4
- data/docs/superpowers/plans/2026-10-25-failure-video-replay.md +400 -0
- data/docs/superpowers/plans/2026-10-26-parity-cleanup.md +233 -0
- data/docs/superpowers/plans/2026-10-28-failure-card.md +295 -0
- data/docs/superpowers/specs/2026-10-25-failure-video-replay-design.md +214 -0
- data/docs/superpowers/specs/2026-10-28-failure-card-design.md +210 -0
- data/lib/commands/scaffolding_commands.rb +3 -3
- data/lib/commands/utility_commands.rb +36 -6
- data/lib/generators/cucumber/templates/cucumber.tt +1 -1
- data/lib/generators/cucumber/templates/partials/appium_env.tt +6 -0
- data/lib/generators/cucumber/templates/partials/selenium_env.tt +6 -0
- data/lib/generators/cucumber/templates/partials/watir_env.tt +6 -0
- data/lib/generators/helper_generator.rb +5 -1
- data/lib/generators/infrastructure/azure_generator.rb +1 -0
- data/lib/generators/infrastructure/github_generator.rb +1 -0
- data/lib/generators/infrastructure/templates/azure_pipelines.tt +26 -2
- data/lib/generators/infrastructure/templates/github.tt +31 -1
- data/lib/generators/infrastructure/templates/github_appium.tt +50 -1
- data/lib/generators/invoke_generators.rb +20 -2
- data/lib/generators/rspec/rspec_generator.rb +0 -1
- data/lib/generators/templates/common/gemfile.tt +1 -1
- data/lib/generators/templates/common/git_ignore.tt +3 -0
- data/lib/generators/templates/common/partials/mobile_config.tt +5 -0
- data/lib/generators/templates/common/partials/web_config.tt +6 -1
- data/lib/generators/templates/common/read_me.tt +80 -2
- data/lib/generators/templates/helpers/browser_helper.tt +5 -1
- data/lib/generators/templates/helpers/driver_helper.tt +1 -0
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +4 -1
- data/lib/generators/templates/helpers/spec_helper.tt +8 -0
- data/lib/generators/templates/helpers/video_helper.tt +338 -171
- data/lib/ruby_raider/ffmpeg.rb +280 -0
- data/lib/ruby_raider.rb +2 -3
- data/lib/scaffolding/project_detector.rb +1 -3
- data/lib/scaffolding/scaffolding.rb +0 -1
- data/lib/utilities/utilities.rb +15 -2
- data/lib/version +1 -1
- data/ruby_raider.gemspec +3 -1
- data/sig/commands/utility_commands.rbs +4 -1
- data/sig/generators/invoke_generators.rbs +2 -3
- data/sig/ruby_raider/ffmpeg.rbs +23 -0
- data/sig/utilities/utilities.rbs +1 -0
- data/spec/commands/raider_commands_spec.rb +56 -1
- data/spec/generators/fixtures/video_helper_stubs/selenium-webdriver.rb +28 -0
- data/spec/generators/video_helper_spec.rb +608 -0
- data/spec/integration/commands/browser_update_after_creation_spec.rb +7 -6
- data/spec/integration/commands/utility_commands_spec.rb +2 -2
- data/spec/integration/content/azure_content_spec.rb +48 -3
- data/spec/integration/content/ci_content_spec.rb +115 -5
- data/spec/integration/content/common_content_spec.rb +8 -0
- data/spec/integration/content/config_content_spec.rb +14 -0
- data/spec/integration/content/cucumber_reporter_content_spec.rb +22 -1
- data/spec/integration/content/gemfile_content_spec.rb +2 -3
- data/spec/integration/content/helper_content_spec.rb +146 -0
- data/spec/integration/content/page_content_spec.rb +0 -1
- data/spec/integration/content/readme_content_spec.rb +65 -0
- data/spec/integration/content/rspec_reporter_content_spec.rb +2 -2
- data/spec/integration/content/test_content_spec.rb +2 -3
- data/spec/integration/end_to_end_features_spec.rb +0 -2
- data/spec/integration/end_to_end_spec.rb +252 -26
- data/spec/integration/generators/azure_generator_spec.rb +7 -6
- data/spec/integration/generators/common_generator_spec.rb +1 -1
- data/spec/integration/generators/invoke_generators_spec.rb +52 -0
- data/spec/integration/scaffolding_e2e_spec.rb +13 -24
- data/spec/scaffolding/scaffold_project_detector_spec.rb +0 -1
- data/spec/scaffolding/scaffolding_features_spec.rb +0 -1
- data/spec/system/selenium_spec.rb +0 -1
- data/spec/system/watir_spec.rb +0 -1
- data/spec/utilities/ffmpeg_spec.rb +424 -0
- data/spec/utilities/utilities_spec.rb +19 -2
- data/spec/utilities/video_command_spec.rb +120 -0
- metadata +49 -11
- data/lib/commands/adopt_commands.rb +0 -42
- data/lib/commands/loaded_commands.rb +0 -9
- data/lib/commands/plugin_commands.rb +0 -32
- data/sig/commands/adopt_commands.rbs +0 -8
- data/sig/commands/loaded_commands.rbs +0 -5
- data/sig/commands/plugin_commands.rbs +0 -9
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# Parity Cleanup Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Make ruby_raider, raider_reporter and raider_desktop agree on what Raider supports. Remove Capybara, Minitest and Adopt. Fix desktop commands the gem rejects. Align versions. Correct the docs.
|
|
6
|
+
|
|
7
|
+
**Architecture:** This is a cleanup only, with no new features. The gem is the source of truth for what Raider generates and accepts. Desktop and docs move to match it. The one behaviour change in the gem is that `raider` exits non-zero on errors.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Ruby/Thor/RSpec (gem, reporter); Electron/React/TypeScript/Vitest/Playwright (desktop).
|
|
10
|
+
|
|
11
|
+
**Spec:** No written spec. The scope was approved in chat on 2026-10-26: the inventory list "cleanup proposal" sections 1–6. That approval excluded anything that overlaps the no-code desktop session.
|
|
12
|
+
|
|
13
|
+
## Repos and branches
|
|
14
|
+
|
|
15
|
+
- ruby_raider: `/Users/augustingottlieb/ruby_raider`, branch `feat/failure-video-replay`. Cleanup commits go on top of it.
|
|
16
|
+
- raider_reporter: `/Users/augustingottlieb/raider_reporter`, branch `feature/video-attachments`.
|
|
17
|
+
- raider_desktop: worktree `/Users/augustingottlieb/raider_desktop-parity-cleanup`, branch `feature/parity-cleanup`. It is based on the rebased `feature/video-player`, which is on top of origin/feature/14-quarantine 8fc5fb8.
|
|
18
|
+
|
|
19
|
+
## Global Constraints
|
|
20
|
+
|
|
21
|
+
- **Never touch** `/Users/augustingottlieb/raider_desktop` or `/Users/augustingottlieb/raider_desktop/.worktrees/*`. Another session owns them.
|
|
22
|
+
- **Desktop files owned by the other session.** Do NOT edit:
|
|
23
|
+
- `src/main/handlers/{appState,getSuites,createSuite,deleteSuite,deleteTest,suitePersistence}.ts`
|
|
24
|
+
- `project/setActiveProject.ts`, `project/projectLayout.ts`
|
|
25
|
+
- `recording/*`
|
|
26
|
+
- `io/{import*,export*,readDirectory}.ts`
|
|
27
|
+
- `trace/*`, `testing/{runRecording,runSuite}.ts`, `handlers/tests/*`
|
|
28
|
+
- `src/main/codegen/*`, `frameworkTemplates.ts`
|
|
29
|
+
- `pages/Overview`, `pages/Recorder`
|
|
30
|
+
- `components/{MainRecorderPanel,CommandList,CommandBlock,TestSuitePanel,ProjectTestsPanel}`
|
|
31
|
+
- `Stores/{recorderStore,projectStore}`
|
|
32
|
+
- e2e `recorder-page`, `recorder-controls`, `recorder-import-export`, `settings-recording`, `project-tests`
|
|
33
|
+
- **Shared desktop files:** `src/main/index.ts`, `preload/index.ts`, `foundation/Types/window.ts`, `en.json`, `e2e/helpers/navigation.ts`.
|
|
34
|
+
- Deletions only, except the navigation helper change in Task 5.
|
|
35
|
+
- In `en.json`, never edit keys under `recorder.*` or `overview.*`. Add only new top-level keys.
|
|
36
|
+
- **Gem files the other session's `feat/recorder-settings` branch changes.** Do NOT edit `web_config.tt`, `browser_helper.tt` or `helpers/partials/selenium_driver.tt`.
|
|
37
|
+
- **Keep** Capybara/Minitest *detection* (`projectLayout.ts`) and `test/integration/codegen/testFile.test.ts`.
|
|
38
|
+
- **Keep** the `refreshRecorderSettings` calls in `config/update{BrowserUrl,BrowserType,Headless,Timeout}.ts`.
|
|
39
|
+
- **Ruby versions:**
|
|
40
|
+
- Minimum supported Ruby is 3.2.0 (gem `ruby_raider.gemspec:12` and reporter gemspec).
|
|
41
|
+
- The desktop auto-install target is Ruby 3.4.1.
|
|
42
|
+
- **raider_reporter pin** in generated projects: `'~> 0.10'`.
|
|
43
|
+
- **No pushes, no publishing.** Stage files explicitly. Leave the user's untracked files alone.
|
|
44
|
+
|
|
45
|
+
## Review Focus
|
|
46
|
+
|
|
47
|
+
1. A desktop action that runs a gem command with a bad flag must surface an error, not a success toast. Covered by Tasks 1 and 4.
|
|
48
|
+
2. `raider new` with an unknown framework must exit non-zero with a clear message, not a NameError backtrace and exit 0. Task 1.
|
|
49
|
+
3. Users on Ruby 3.1 opening desktop must be told to upgrade (minimum 3.2.0). Task 6.
|
|
50
|
+
4. Cucumber "Re-run Failed" must re-run only the failed scenarios after a failing run in a freshly generated project. Task 2.
|
|
51
|
+
5. Removing Capybara/Minitest must not break opening a legacy Capybara/Minitest project. The detection is kept and the codegen test stays green. Task 5.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### Task 1: gem — non-zero exit on errors, remove dead Adopt
|
|
56
|
+
|
|
57
|
+
**Files:**
|
|
58
|
+
- Modify: `lib/ruby_raider.rb` and the Thor command classes in `lib/commands/*.rb`: add `def self.exit_on_failure? = true` (or the repo's Ruby style).
|
|
59
|
+
- Modify: `lib/generators/invoke_generators.rb`: validate `framework` (`rspec`, `cucumber`) and `automation` (`selenium`, `watir`, `appium`, `ios`, `android`, `cross_platform`). Use the values the menu actually accepts; confirm them in `menu_generator.rb`. An unknown value raises `Thor::Error` naming the bad key and the allowed values.
|
|
60
|
+
- Delete: `lib/commands/adopt_commands.rb`, `sig/commands/adopt_commands.rbs`. Also check `plugin_commands.rb`: if it is unregistered and dead, delete it and its sig the same way; if not, leave it.
|
|
61
|
+
- Test: `spec/commands/raider_commands_spec.rb`
|
|
62
|
+
|
|
63
|
+
- [ ] Step 1: failing specs, running `bin/raider` as a subprocess with Ruby 3.4.1 and gems installed:
|
|
64
|
+
- `raider u debug on` → exit status ≠ 0, stderr includes `Could not find command "debug"`
|
|
65
|
+
- `raider new x -p framework:minitest automation:selenium` → exit ≠ 0, output names `framework` and lists `rspec`, `cucumber`, and no directory `x` is created
|
|
66
|
+
- `raider version` → exit 0
|
|
67
|
+
- [ ] Step 2: run them and see FAIL.
|
|
68
|
+
- [ ] Step 3: implement.
|
|
69
|
+
- [ ] Step 4: run `bundle exec rspec` on tracked spec files (exclude the user's untracked `spec/test_page_spec.rb`), rubocop on changed files, and `bundle exec steep check`. Expected: PASS. The known `azure_generator_spec.rb:75` failure predates this branch.
|
|
70
|
+
- [ ] Step 5: commit "Exit non-zero on CLI errors; validate new-project options; drop dead adopt command".
|
|
71
|
+
|
|
72
|
+
### Task 2: gem — Cucumber writes rerun.txt; reporter pin ~> 0.10
|
|
73
|
+
|
|
74
|
+
**Files:**
|
|
75
|
+
- Modify: `lib/generators/cucumber/templates/cucumber.tt` → `default: --format pretty --format rerun --out rerun.txt --publish-quiet --retry 0`
|
|
76
|
+
- Modify: the generated `.gitignore` template (find it) to ignore `rerun.txt`, if a gitignore is generated.
|
|
77
|
+
- Modify: `lib/generators/templates/common/gemfile.tt:16` → `gem 'raider_reporter', '~> 0.10'`
|
|
78
|
+
- Modify: README.md: remove the "0.9 still works" wording if present, and fix line 30 ("API backend" / "programmatic invocation" → CLI flags).
|
|
79
|
+
- Test: `spec/integration/content/cucumber_reporter_content_spec.rb` (or the cucumber.yml content spec), `gemfile_content_spec.rb`
|
|
80
|
+
|
|
81
|
+
- [ ] Step 1: failing content specs:
|
|
82
|
+
- `cucumber.yml` includes `--format rerun --out rerun.txt`
|
|
83
|
+
- Gemfile includes `gem 'raider_reporter', '~> 0.10'`
|
|
84
|
+
- [ ] Step 2: FAIL. Step 3: implement. Step 4: content specs pass.
|
|
85
|
+
- [ ] Step 5: run the cucumber e2e examples in `spec/integration/end_to_end_spec.rb` (the ones not tagged `:video`), so the rerun formatter doesn't break a real run.
|
|
86
|
+
- [ ] Step 6: commit "Write Cucumber rerun.txt and require raider_reporter 0.10 in generated projects".
|
|
87
|
+
|
|
88
|
+
### Task 3: reporter — docs match 0.10
|
|
89
|
+
|
|
90
|
+
**Files:**
|
|
91
|
+
- Modify: `docs/schema-v1.md:68`. Size caps become: 5 MB per non-video attachment, 50 MB per `video/*`; upload budget 45 MB and 1000 files, non-video first.
|
|
92
|
+
- Modify: `README.md`. Add `RAIDER_EXEC_SUMMARY` to the options table, with its meaning taken from `configuration.rb:23`.
|
|
93
|
+
- Modify: `examples/github_actions.yml`. Run tests the way generated CI does (`bundle exec raider-reporter exec -- …` if that is the generated form; check `ruby_raider/lib/generators/infrastructure/templates/github.tt`).
|
|
94
|
+
- [ ] Steps:
|
|
95
|
+
1. Edit the three files.
|
|
96
|
+
2. Run `bundle exec rspec`. Expected: 456+ pass.
|
|
97
|
+
3. Commit "Document 0.10 size caps, exec summary and exec wrapper".
|
|
98
|
+
|
|
99
|
+
### Task 4: desktop — commands the gem accepts
|
|
100
|
+
|
|
101
|
+
**Files:**
|
|
102
|
+
- Modify: `src/main/handlers/config/updateBrowserOptions.ts`. Command becomes `raider u browser_options <each option, shell-quoted>`. With an empty list it becomes `raider u browser_options --delete`, if that clears; check `utility_commands.rb`.
|
|
103
|
+
- Delete:
|
|
104
|
+
- `src/main/handlers/config/updateDebugMode.ts` and its IPC registration (`src/main/index.ts`)
|
|
105
|
+
- its preload method and `window.ts` type
|
|
106
|
+
- the `settings.debug` keys in `en.json`
|
|
107
|
+
- its tests
|
|
108
|
+
- Modify:
|
|
109
|
+
- `src/main/handlers/project/scaffoldGenerate.ts` and `src/shared/types/scaffoldTypes.ts`: keep only `generate <type> <name> [--path]` for `page|feature|spec|helper|steps|component|scaffold`. Drop `uses`, `from`, `ai`, `delete`, `dryRun`, `destroy`, `from_url` and `crud`.
|
|
110
|
+
- `src/main/handlers/runRubyRaider.ts` and `src/shared/types/projectCreationTypes.ts`: drop `-r <reporter>`, `--skip_ci`, `--ruby_version` and their fields.
|
|
111
|
+
- Delete: `src/main/handlers/ruby/installRaider.ts`, its IPC, preload, `window.ts` entry and `ipcContract.test.ts` line.
|
|
112
|
+
- Test: unit tests next to each handler, following existing vitest patterns in `test/`.
|
|
113
|
+
|
|
114
|
+
- [ ] Step 1: failing vitest specs:
|
|
115
|
+
- updateBrowserOptions builds `raider u browser_options no-sandbox 'window-size=1280,800'` (each arg quoted)
|
|
116
|
+
- scaffoldGenerate for `{generate:'page', name:'login', path:'pages'}` builds `raider g page login --path pages`, and the type has no `crud`
|
|
117
|
+
- runRubyRaider never emits `-r`, `--skip_ci` or `--ruby_version`
|
|
118
|
+
- [ ] Step 2: FAIL. Step 3: implement.
|
|
119
|
+
- [ ] Step 4: `npx vitest run`, `npm run typecheck`, `npx eslint <changed files>`. Expected: PASS.
|
|
120
|
+
- [ ] Step 5: commit "Send only commands and flags the raider gem accepts".
|
|
121
|
+
|
|
122
|
+
### Task 5: desktop — remove Capybara, Minitest and Adopt; fix stale e2e
|
|
123
|
+
|
|
124
|
+
**Files:**
|
|
125
|
+
- Delete: `src/renderer/src/components/Editor/completions/capybaraCompletions.ts`
|
|
126
|
+
- Modify:
|
|
127
|
+
- `completions/registerProviders.ts`: remove the capybara and minitest branches and imports
|
|
128
|
+
- `completions/testFrameworkCompletions.ts`: remove `registerMinitestCompletions`
|
|
129
|
+
- `src/shared/types/raiderConfig.ts`: `automation` becomes `'Selenium' | 'Watir' | 'Appium'` (check what the gem's config writes and match case); `test` becomes `'Rspec' | 'Cucumber'`
|
|
130
|
+
- Modify: `test/integration/runRecording.test.ts`. Remove or retitle the minitest case only if it is not in the other session's ownership; `runRecording.ts` is theirs, but this test file isn't listed. If in doubt, leave it and note it.
|
|
131
|
+
- e2e:
|
|
132
|
+
- delete `e2e/adopt-page.spec.ts` and `e2e/minitest-support.spec.ts`
|
|
133
|
+
- remove `mockAdoptAnalyze` from `e2e/helpers/ipc-mock.ts`
|
|
134
|
+
- `e2e/new-project-page.spec.ts`: drop the Capybara, Minitest, Appium/mobile and Applitools expectations, so the lists match `pages/New/index.tsx:16-19`
|
|
135
|
+
- `e2e/app-launch.spec.ts`: expect the Welcome page ("Welcome to Ruby Raider!") as the start screen
|
|
136
|
+
- `e2e/helpers/navigation.ts`: a helper that gets from the Welcome page into the main layout. Send the diff to the other session (`raider-desktop-e8`) before committing.
|
|
137
|
+
- Settings pages (agreed with the other session on 2026-10-26):
|
|
138
|
+
- **Keep** `pages/ProjectSettings`; its Plan 4 will remount it. Only remove its debug toggle (Task 4 deletes `updateDebugMode`).
|
|
139
|
+
- **Delete** `pages/RecorderSettings`, plus only the `en.json` `settings.*` keys that RecorderSettings uses alone. Before deleting a key, grep that ProjectSettings and everything else don't use it.
|
|
140
|
+
- **Delete** e2e `settings-page.spec.ts` and `settings-project.spec.ts`.
|
|
141
|
+
- **Do not touch** `settings-recording.spec.ts`.
|
|
142
|
+
- `e2e/helpers/ipc-mock.ts`: the other session is editing it now. Make only the `mockAdoptAnalyze` deletion.
|
|
143
|
+
- `e2e/app-launch.spec.ts`: change only the start-screen expectation. Overview's default tab is changing on the other branch, so assert nothing about Overview tabs.
|
|
144
|
+
- After committing, send the other session the exact diff for `ipc-mock.ts`, `app-launch.spec.ts` and `navigation.ts`.
|
|
145
|
+
- Test: the full e2e suite; record before and after pass counts (baseline on this branch: 97 passed, 91 failed).
|
|
146
|
+
|
|
147
|
+
- [ ] Step 1: make the removals.
|
|
148
|
+
- [ ] Step 2: run `npx vitest run`, `npm run typecheck` and `npx electron-vite build`.
|
|
149
|
+
- [ ] Step 3: run `npx playwright test`. Expected: no new failures. List every remaining failure with a one-line cause, marking which belong to the other session's specs.
|
|
150
|
+
- [ ] Step 4: commit, in two commits: "Remove Capybara, Minitest and Adopt from desktop", then "Update e2e for the Welcome start page".
|
|
151
|
+
|
|
152
|
+
### Task 6: desktop — Ruby 3.2 floor, 3.4.1 install
|
|
153
|
+
|
|
154
|
+
**Files:**
|
|
155
|
+
- Modify: `src/main/utils/rubyVersion.ts`: `MIN_MINOR = 2`
|
|
156
|
+
- Modify: `src/main/handlers/ruby/installRbenvAndRuby.ts`: 3.1.6 → 3.4.1 everywhere
|
|
157
|
+
- Modify: `isRubyInstalled.ts`, **version-floor edits only**. The other branch rewrote its command building in commit 2a3806f to use `src/main/utils/rubyCommand.ts` builders. Don't change any command string or shape in that file. If the floor check comes from `rubyVersion.ts`, leave this file untouched.
|
|
158
|
+
- Modify: `src/main/handlers/ruby/checkRubyDependencies.ts`: also check `watir`, following the existing pattern for the other gems. The other session offered this file to us.
|
|
159
|
+
- Modify: `en.json`: the install-guide strings that say "3.0.0 or higher" or "Ruby 3+" now say "3.2 or higher". Edit those keys only; they are not under recorder.* or overview.*.
|
|
160
|
+
- Test: `test/**/rubyVersion*.test.ts` (find or create)
|
|
161
|
+
|
|
162
|
+
- [ ] Step 1: failing tests: `isRubyVersionSufficient(3,1)` is false, `(3,2)` is true, and `formatMinimumVersion()` is `'3.2.0'`.
|
|
163
|
+
- [ ] Step 2: FAIL. Step 3: implement. Step 4: vitest and typecheck pass.
|
|
164
|
+
- [ ] Step 5: commit "Require Ruby 3.2 and install Ruby 3.4.1".
|
|
165
|
+
|
|
166
|
+
### Task 7: docs — desktop README and backend docs match the gem
|
|
167
|
+
|
|
168
|
+
**Files:**
|
|
169
|
+
- `README.md`:
|
|
170
|
+
- Remove Capybara, Minitest and Adopt.
|
|
171
|
+
- Set Ruby 3.2+.
|
|
172
|
+
- Say new-project creates web projects only.
|
|
173
|
+
- Remove claims for viewport presets and mobile capabilities if Task 5 deleted those pages; keep them if the pages stayed.
|
|
174
|
+
- Explain that the version footer shows the globally installed gem.
|
|
175
|
+
- Explain that failure video needs `RAIDER_FFMPEG_PATH` until the ffmpeg release exists, and where to see it (Project → Dashboard → failed card → Failure video).
|
|
176
|
+
- Explain that desktop's "push to Longship" and raider_reporter's upload are separate paths.
|
|
177
|
+
- Reconcile Windows: native win32 code exists, but the guide recommends WSL. State what is supported now, as the code shows.
|
|
178
|
+
- `BACKEND_PARITY.md` and `RUBY_RAIDER_INTEGRATION.md`: remove the gem surface that no longer exists:
|
|
179
|
+
- `--visual`, `--performance`, `-r allure/junit/json`
|
|
180
|
+
- `--skip_ci`, `--skip_allure`, `gitlab`
|
|
181
|
+
- `g from_url`, `g destroy`, `--crud`, `--uses`, `--ai`
|
|
182
|
+
- `u llm`, `u debug`, `raider pm`
|
|
183
|
+
- Allure, Adopt, Capybara, Minitest
|
|
184
|
+
|
|
185
|
+
Every remaining command and flag must exist in the gem's `lib/commands` and `lib/ruby_raider.rb`.
|
|
186
|
+
- `CLAUDE.md`: change "Ruby 3.0.0+" to "Ruby 3.2.0+".
|
|
187
|
+
- [ ] Steps:
|
|
188
|
+
1. Edit the files.
|
|
189
|
+
2. For each command example left in the two backend docs, check the gem accepts it, using `bin/raider help <cmd>` against the ruby_raider checkout.
|
|
190
|
+
3. Commit "Align desktop docs with the raider gem".
|
|
191
|
+
|
|
192
|
+
### Task 8: desktop — Overview settings match the generated config
|
|
193
|
+
|
|
194
|
+
The other session handed these to us. It owns the Overview tab bar, the default tab and the Tests tab body, so stay away from those lines in `pages/Overview/index.tsx`.
|
|
195
|
+
|
|
196
|
+
**Files:**
|
|
197
|
+
- Modify: `pages/Overview/index.tsx`, scaffold/settings sections only:
|
|
198
|
+
- The browser dropdown offers only `chrome` and `firefox` (around line 570). Generated config has `browser_arguments` for those two only.
|
|
199
|
+
- Remove the "Custom Tag" run mode option and its `runRakeTask` path, keeping Smoke and Regression. Remove the `en.json` key `overview.settings.runMode.custom`; this is the one allowed deletion under `overview.*`, agreed with the other session.
|
|
200
|
+
- The timeout initial value and the browser-options TagInput read back from the project config, not the `useState(30)` / empty defaults.
|
|
201
|
+
- Modify: `src/main/handlers/config/getProjectConfig.ts`: also return `timeout` (number) and `browserArguments` (string[] for the current browser), read from `config/config.yml`. That is `config.timeout` and `config.browser_arguments[config.browser]`.
|
|
202
|
+
- Test: the unit test for `getProjectConfig` (find or create): given a config.yml with `timeout: 10`, `browser: chrome`, and `browser_arguments: {chrome: [no-sandbox]}`, it returns `timeout: 10` and `browserArguments: ['no-sandbox']`. Add a renderer test for the Overview settings, if one exists, asserting the browser options are exactly Chrome and Firefox.
|
|
203
|
+
- [ ] Steps:
|
|
204
|
+
1. Write the failing tests.
|
|
205
|
+
2. Implement.
|
|
206
|
+
3. Run vitest, typecheck and eslint on the changed files.
|
|
207
|
+
4. Commit "Show the project's real timeout and browser args; offer only supported browsers".
|
|
208
|
+
|
|
209
|
+
## Not in this plan
|
|
210
|
+
- These are the other session's (recorder area):
|
|
211
|
+
- recorder export, Watir recording, recorded runs outside the project, the wait keys
|
|
212
|
+
- After this plan is done, ask the user again about the next parity item. That is item #2: steps, stdout and retries in run.json.
|
|
213
|
+
|
|
214
|
+
### Task 9: desktop — fix stale e2e outside the other session's area
|
|
215
|
+
|
|
216
|
+
Agreed with the other session on 2026-10-26.
|
|
217
|
+
|
|
218
|
+
**Files:** e2e `app-launch` (the nav-links test), `navigation`, `landing-page`, `new-project-page`, `error-states`, `dashboard-page`, `dashboard-trace`, `full-workflow`.
|
|
219
|
+
- Use `enterMainLayout()` (`e2e/helpers/navigation.ts`) where a test needs the main layout.
|
|
220
|
+
- Make the "Open" locators unambiguous.
|
|
221
|
+
- Update text and selectors to match the current UI. Change the tests, not the UI.
|
|
222
|
+
|
|
223
|
+
**Rules:**
|
|
224
|
+
- Each fix must match what the app really does now. Never loosen an assertion until it means nothing.
|
|
225
|
+
- If a test covers a feature that no longer exists, delete the test and list it in the report.
|
|
226
|
+
|
|
227
|
+
**Deferred:** `overview-page`, `file-editor-page`, `scaffold-panel` and `test-execution-features` wait until the other session's branch `feature/recorder-project-files` lands and this branch is rebased. Then fix only the failures this branch caused: the Welcome start screen, the duplicate "Open" match and changed testids.
|
|
228
|
+
|
|
229
|
+
- [ ] Steps:
|
|
230
|
+
1. Run the listed specs (after `npx electron-vite build`).
|
|
231
|
+
2. Fix them.
|
|
232
|
+
3. Re-run the listed specs until they pass.
|
|
233
|
+
4. Commit "Update e2e to the current desktop UI".
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Failed-Test Card Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** In raider_desktop's project Dashboard and raider_saas, a failed test opens as one side-by-side card:
|
|
6
|
+
- the title
|
|
7
|
+
- the screenshot on the left
|
|
8
|
+
- the message and "Failed at file:line" on the right
|
|
9
|
+
- a stack trace that opens on demand, with project frames highlighted and other frames folded
|
|
10
|
+
|
|
11
|
+
On desktop, "Failed at" opens the file at that line.
|
|
12
|
+
|
|
13
|
+
**Architecture:**
|
|
14
|
+
- raider_reporter relativizes in-project backtrace frames, so a UI can recognise project frames without knowing the root.
|
|
15
|
+
- Each app gets a small pure frame helper (parse, classify, fold) and renders the card from it.
|
|
16
|
+
- SaaS stores `app_frame` per result, and its API presenter prefers that stored value.
|
|
17
|
+
|
|
18
|
+
**Tech Stack:** Ruby gem (RSpec), Electron + React + TypeScript (vitest, Playwright), Rails 8 + Hotwire + Tailwind (RSpec, SQLite and Postgres).
|
|
19
|
+
|
|
20
|
+
**Spec:** `docs/superpowers/specs/2026-10-28-failure-card-design.md` (in this repo). Read its "Decisions" and "Builder-check rulings" sections.
|
|
21
|
+
|
|
22
|
+
## Global Constraints
|
|
23
|
+
|
|
24
|
+
**Workspaces.** Create each worktree before its first task and work only there:
|
|
25
|
+
|
|
26
|
+
| Repo | Worktree | Branch | Base |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| raider_reporter | `~/raider_reporter-failure-card` | `feature/failure-card` | `feature/video-attachments` |
|
|
29
|
+
| raider_desktop | `~/raider_desktop-failure-card` | `feature/failure-card` | `feature/parity-cleanup` |
|
|
30
|
+
| raider_saas | `~/raider_saas-worktrees/failure-card` | `feature/failure-card` | `origin/main` (fetch first) |
|
|
31
|
+
|
|
32
|
+
- **Never touch** `~/raider_desktop`, `~/raider_saas` or `~/raider_reporter` working trees.
|
|
33
|
+
- **Desktop files that are off-limits:** recorder, codegen, `projectLayout.ts`, `rubyCommand.ts`, `appState`, suites, Overview tab bar/Tests tab, and `components/RecordingDashboard`. RecordingDashboard must render exactly as today.
|
|
34
|
+
- **SaaS files that are off-limits:** analytics files, `config/routes.rb`, `app/views/projects/show.html.erb` and `app/services/search/*`.
|
|
35
|
+
- **SaaS rules:**
|
|
36
|
+
- Run `source dev-env.sh` before any Ruby command.
|
|
37
|
+
- No inline `style=` or `on*=` attributes (CSP; enforced by `spec/requests/no_inline_handlers_spec.rb`).
|
|
38
|
+
- RuboCop omakase.
|
|
39
|
+
- SQL must work on SQLite and Postgres, with bound params.
|
|
40
|
+
- Run the full suite on both adapters. For Postgres, touch only `raider_*` DBs: `DB_ADAPTER=postgresql RAILS_ENV=test bin/rails db:create db:schema:load && DB_ADAPTER=postgresql bundle exec rspec`.
|
|
41
|
+
- Don't use ports 3000 or 3001.
|
|
42
|
+
- Attachments only via `attachment_path`.
|
|
43
|
+
- No Co-Authored-By or "Generated with" trailers.
|
|
44
|
+
- **Git:**
|
|
45
|
+
- Commit on the task branch only.
|
|
46
|
+
- Never stash, `checkout <sha>`, `reset --hard`, rebase or push.
|
|
47
|
+
- Restore desktop `playwright-report/` and `test-results/` if a run modifies them.
|
|
48
|
+
- **`app_frame` length cap:** 500 characters (`AppFrame::MAX_LENGTH`, `ErrorGroup::SAMPLE_APP_FRAME_LIMIT`).
|
|
49
|
+
- **Display string:** "Failed at" shows `file:line` only. The full `app_frame` goes in the `title` attribute.
|
|
50
|
+
- **Screenshot slot:** the attachment named `screenshot`, else the first `image/*` attachment, else none (the right column takes the full width).
|
|
51
|
+
- **Browser:** shown only when the run's `environment["browser"]` is present.
|
|
52
|
+
- **Failing statuses:** desktop `failed` and `broken`; SaaS `TestResult::FAILING_STATUSES`. Other statuses render as today.
|
|
53
|
+
|
|
54
|
+
**Frame rules.** Both apps use these exact rules:
|
|
55
|
+
- `parseFrame(s)` matches `/\A((?:[A-Za-z]:)?[^:]+):(\d+)(?::in .*)?\z/` and returns `{ file, line }`, or null when there is no match.
|
|
56
|
+
- A frame is a **project** frame if either:
|
|
57
|
+
- its file equals the parsed `app_frame` file, or
|
|
58
|
+
- its file is relative (does not start with `/` or `X:`), does not start with `vendor/`, `.bundle/` or `<internal:`, and does not contain `/gems/`.
|
|
59
|
+
- Every other frame (including unparseable lines) is **other**.
|
|
60
|
+
- `foldFrames(lines, appFrame)` returns ordered segments. Each project line is `{ kind: 'project', text }`. Each maximal run of consecutive other lines is `{ kind: 'folded', lines }`, rendered as a toggle labelled "… N more" (N = lines.length) that expands to the dimmed lines.
|
|
61
|
+
|
|
62
|
+
## Review Focus
|
|
63
|
+
|
|
64
|
+
1. **Failure with an empty or missing backtrace and no `app_frame`** (RSpec `aggregate_failures`): the card shows the message only, with no "Failed at" and no trace toggle. Tested in Tasks 2, 4 and 6.
|
|
65
|
+
2. **`app_frame` points at a file that no longer exists in the project** (renamed test): "Failed at" is plain text, not a link, on desktop. Tested in Task 3.
|
|
66
|
+
3. **Windows-style frames** `C:/proj/spec/a_spec.rb:12:in 'x'`: the parser returns `{ file: 'C:/proj/spec/a_spec.rb', line: 12 }`, and the frame is "other" unless it equals the `app_frame` file. Tested in Tasks 2 and 6.
|
|
67
|
+
4. **SaaS result whose attachments expired (30 days)** or whose run has `results_purged_at`: the card renders without a screenshot slot and shows the existing expired note. No 500 error. Tested in Task 6.
|
|
68
|
+
5. **A long trace that is all "other"** (no project frames, e.g. a Selenium timeout raised in a gem): it folds into a single "… N more" toggle, and N matches the line count. Tested in Tasks 2 and 6.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### Task 1: reporter — project-relative backtraces
|
|
73
|
+
|
|
74
|
+
**Files:**
|
|
75
|
+
- Modify: `lib/raider_reporter/app_frame.rb`
|
|
76
|
+
- Modify: `lib/raider_reporter/rspec/formatter.rb` (`failure_for`)
|
|
77
|
+
- Modify: `lib/raider_reporter/cucumber/formatter.rb` (failure builder near the `backtrace:` line)
|
|
78
|
+
- Test: `spec/raider_reporter/app_frame_spec.rb`, plus the RSpec and Cucumber formatter specs under `spec/raider_reporter/`
|
|
79
|
+
|
|
80
|
+
**Interfaces:**
|
|
81
|
+
- Produces: `AppFrame.relativize(backtrace, root:) -> Array<String>`. Each line whose path resolves to a file inside `root` (using the same `inside_root` and symlink logic as `from`, and excluding `Gem.path`, rubylibdir, `vendor/` and `.bundle/`) is rewritten to `relative/path.rb:LINE…`, keeping the suffix. All other lines are returned unchanged. `nil` or empty input returns `[]`, and `root` nil returns the input unchanged.
|
|
82
|
+
- run.json and payload shape unchanged: `failure.backtrace` is still `Array<String>`, max 50.
|
|
83
|
+
|
|
84
|
+
- [ ] **Step 1: Write failing specs in `app_frame_spec.rb`.** Create real files in a tmp project root.
|
|
85
|
+
- `"#{root}/spec/a_spec.rb:3:in 'x'"` becomes `"spec/a_spec.rb:3:in 'x'"`.
|
|
86
|
+
- A gem path line is unchanged.
|
|
87
|
+
- A `<internal:kernel>` line is unchanged.
|
|
88
|
+
- A path through a symlinked root is relativized.
|
|
89
|
+
- `relativize(nil, root:)` returns `[]`.
|
|
90
|
+
- [ ] **Step 2: Formatter specs.**
|
|
91
|
+
- RSpec: the failing example's backtrace in run.json has the spec file as `spec/…_spec.rb:N` (relative).
|
|
92
|
+
- Cucumber fixture under `spec/fixtures/cucumber`:
|
|
93
|
+
- the backtrace contains the step definition as `features/step_definitions/…_steps.rb:N`
|
|
94
|
+
- `app_frame` starts with `features/step_definitions/` (pins that Cucumber plumbing is skipped)
|
|
95
|
+
- [ ] **Step 3: Run** `bundle exec rspec spec/raider_reporter/app_frame_spec.rb` and the formatter specs. Expected: FAIL.
|
|
96
|
+
- [ ] **Step 4: Implement `relativize`.** Apply it in both formatters to the stored backtrace before `.first(MAX_BACKTRACE)`, with `root: RaiderReporter.project_root || Dir.pwd`, wrapped like `app_frame_for` (rescue to the unmodified backtrace). RSpec keeps `backtrace_formatter` first.
|
|
97
|
+
- [ ] **Step 5: Run** `bundle exec rspec`. Expected: all pass.
|
|
98
|
+
- [ ] **Step 6: Update the docs.** Add a CHANGELOG/README line: "Backtrace frames inside the project are written project-relative."
|
|
99
|
+
- [ ] **Step 7: Commit:** "Write in-project backtrace frames project-relative".
|
|
100
|
+
|
|
101
|
+
### Task 2: desktop — frame helpers and richer result rows
|
|
102
|
+
|
|
103
|
+
**Files:**
|
|
104
|
+
- Create: `src/renderer/src/foundation/stackFrames.ts`
|
|
105
|
+
- Modify: `src/renderer/src/foundation/raiderResults.ts`
|
|
106
|
+
- Modify: `src/renderer/src/foundation/Types/testResult.ts`
|
|
107
|
+
- Test: `test/stackFrames.test.ts`, `test/raiderResults.test.ts`
|
|
108
|
+
|
|
109
|
+
**Interfaces:**
|
|
110
|
+
- Produces in `stackFrames.ts`:
|
|
111
|
+
- `parseFrame(s: string): { file: string; line: number } | null`
|
|
112
|
+
- `isProjectFrame(line: string, appFrame?: string): boolean`
|
|
113
|
+
- `type FrameSegment = { kind: 'project'; text: string } | { kind: 'folded'; lines: string[] }`
|
|
114
|
+
- `foldFrames(lines: string[], appFrame?: string): FrameSegment[]`
|
|
115
|
+
- All follow the Global Constraints rules.
|
|
116
|
+
- Produces in `testResult.ts`:
|
|
117
|
+
- `export interface FailureInfo { message: string; exceptionClass?: string; backtrace: string[]; appFrame?: string }`
|
|
118
|
+
- `TestResult` gains optional `failure?: FailureInfo`, `durationMs?: number` and `browser?: string`.
|
|
119
|
+
- `raiderResults.toRow` changes:
|
|
120
|
+
- `statusDetails.message` is the sanitized message only. The backtrace is no longer appended.
|
|
121
|
+
- It sets `failure` (with the backtrace and `app_frame` from run.json), `durationMs` from `duration_ms`, and `browser` from run.json `run.environment.browser`.
|
|
122
|
+
- The screenshot is the attachment with `name === 'screenshot'`, else the first `image/*`.
|
|
123
|
+
- `RaiderResult` gains `name?` on attachments, plus `duration_ms?` and `failure.exception_class?` / `failure.app_frame?`.
|
|
124
|
+
|
|
125
|
+
- [ ] **Step 1: Write failing `test/stackFrames.test.ts`.**
|
|
126
|
+
- `parseFrame("spec/a_spec.rb:12:in 'x'")` returns `{file:'spec/a_spec.rb',line:12}`.
|
|
127
|
+
- `parseFrame("C:/p/spec/a_spec.rb:12:in 'x'")` returns `{file:'C:/p/spec/a_spec.rb',line:12}`.
|
|
128
|
+
- `parseFrame('garbage')` returns `null`.
|
|
129
|
+
- `isProjectFrame` is true for `pages/login.rb:9`, false for `/usr/lib/ruby/gems/3.4.0/gems/x/y.rb:1`, false for `vendor/bundle/x.rb:1`, and false for `<internal:kernel>:1`.
|
|
130
|
+
- `foldFrames([gemA, gemB, 'spec/a_spec.rb:12', gemC], 'spec/a_spec.rb:12:in x')` returns `[{folded,[gemA,gemB]},{project,'spec/a_spec.rb:12'},{folded,[gemC]}]`.
|
|
131
|
+
- An all-gem input of 7 lines returns one folded segment of 7.
|
|
132
|
+
- `foldFrames([], undefined)` returns `[]`.
|
|
133
|
+
- [ ] **Step 2: Extend `test/raiderResults.test.ts`.**
|
|
134
|
+
- The message no longer contains the backtrace.
|
|
135
|
+
- `failure.appFrame` and `failure.backtrace` are passed through.
|
|
136
|
+
- `durationMs` is set.
|
|
137
|
+
- `browser` comes from `run.environment.browser`, and is undefined when absent.
|
|
138
|
+
- The screenshot prefers `name: 'screenshot'` over an earlier image.
|
|
139
|
+
- A failure with no backtrace gives `failure.backtrace` `[]` and `appFrame` undefined.
|
|
140
|
+
- [ ] **Step 3: Run** `npx vitest run test/stackFrames.test.ts test/raiderResults.test.ts`. Expected: FAIL.
|
|
141
|
+
- [ ] **Step 4: Implement.**
|
|
142
|
+
- [ ] **Step 5: Run** `npx vitest run`, `npm run typecheck` and eslint on the changed files. Expected: green.
|
|
143
|
+
- [ ] **Step 6: Commit:** "Parse stack frames and carry failure details from run.json".
|
|
144
|
+
|
|
145
|
+
### Task 3: desktop — open a file at a line
|
|
146
|
+
|
|
147
|
+
**Files:**
|
|
148
|
+
- Create: `src/renderer/src/foundation/openInEditor.ts`
|
|
149
|
+
- Modify: `src/renderer/src/pages/FileEditor/index.tsx`
|
|
150
|
+
- Modify: `src/renderer/src/components/Editor/index.tsx`
|
|
151
|
+
- Test: `test/openInEditor.test.ts`, `test/FileEditor.test.tsx`
|
|
152
|
+
|
|
153
|
+
**Interfaces:**
|
|
154
|
+
- Produces:
|
|
155
|
+
- `editorTargetFor(projectPath: string, appFrame: string): { fileName: string; filePath: string; line: number } | null`
|
|
156
|
+
- Uses `parseFrame`.
|
|
157
|
+
- Resolves a relative file against `projectPath`; an absolute file is used as is.
|
|
158
|
+
- `fileName` is the basename.
|
|
159
|
+
- Returns null if the frame is unparseable.
|
|
160
|
+
- `async resolveEditorTarget(projectPath, appFrame, api: { readFile }) -> target | null`
|
|
161
|
+
- Returns null when `api.readFile(filePath)` is not `success`.
|
|
162
|
+
- The `file-editor` location state becomes `{ fileName: string; filePath: string; line?: number }`.
|
|
163
|
+
- `Editor` gains an optional prop `revealLine?: number`. On mount, and when the prop changes, it calls `editor.revealLineInCenter(line)` and `editor.setPosition({ lineNumber: line, column: 1 })`, and adds a whole-line decoration, all via Monaco's `onMount`.
|
|
164
|
+
- Callers that pass no `line` behave as today.
|
|
165
|
+
|
|
166
|
+
- [ ] **Step 1: Write failing tests.**
|
|
167
|
+
- `editorTargetFor('/p', "spec/a_spec.rb:12:in 'x'")` returns `{fileName:'a_spec.rb', filePath:'/p/spec/a_spec.rb', line:12}`.
|
|
168
|
+
- An absolute frame is kept as is.
|
|
169
|
+
- `'garbage'` returns `null`.
|
|
170
|
+
- `resolveEditorTarget` returns `null` when the `readFile` mock returns `{success:false}`.
|
|
171
|
+
- `FileEditor` with state `{fileName, filePath, line: 7}` renders `Editor` with `revealLine={7}` (mock `@components/Editor`).
|
|
172
|
+
- `FileEditor` without a `line` passes `revealLine` as undefined.
|
|
173
|
+
- [ ] **Step 2: Run them.** Expected: FAIL.
|
|
174
|
+
- [ ] **Step 3: Implement.**
|
|
175
|
+
- [ ] **Step 4: Run** the full vitest suite and typecheck. Expected: green.
|
|
176
|
+
- [ ] **Step 5: Commit:** "Open the file editor at a given line".
|
|
177
|
+
|
|
178
|
+
### Task 4: desktop — the failure card (layout B) on the project Dashboard
|
|
179
|
+
|
|
180
|
+
**Files:**
|
|
181
|
+
- Create: `src/renderer/src/components/FailureDetails/index.tsx`
|
|
182
|
+
- Modify: `src/renderer/src/components/TestResultCard/index.tsx`
|
|
183
|
+
- Modify: `src/renderer/src/components/ProjectDashboard/index.tsx`
|
|
184
|
+
- Modify: `src/renderer/src/assets/locales/en.json` (or wherever en.json lives)
|
|
185
|
+
- Test: `test/components/FailureDetails.test.tsx`, `test/ProjectDashboard.test.tsx`
|
|
186
|
+
|
|
187
|
+
**Interfaces:**
|
|
188
|
+
- Consumes:
|
|
189
|
+
- from Task 2: `FailureInfo`, `foldFrames`, `parseFrame`, and `TestResult.failure/durationMs/browser`
|
|
190
|
+
- from Task 3: `resolveEditorTarget`, and the `file-editor` state with `line`
|
|
191
|
+
- `FailureDetails` props: `{ failure: FailureInfo; screenshot?: string; durationMs?: number; browser?: string; projectPath?: string; onOpenScreenshot?: () => void }`.
|
|
192
|
+
- `TestResultCard` gains optional props `failure?: FailureInfo`, `durationMs?: number`, `browser?: string` and `projectPath?: string`.
|
|
193
|
+
- When `failure` is set and the status is failed or broken, the expanded body renders `FailureDetails` in place of the message, screenshot and backtrace block.
|
|
194
|
+
- The existing "Failure video" section stays below it, unchanged (`data-testid="result-video"` and its summary text are kept).
|
|
195
|
+
- The existing a11y message parsing still applies to `failure.message`.
|
|
196
|
+
- Only `ProjectDashboard` passes `failure`. RecordingDashboard is untouched.
|
|
197
|
+
|
|
198
|
+
**Layout:**
|
|
199
|
+
- Left column: the screenshot thumbnail (reuse the card's existing modal-open behaviour), about 45% width, shown only when a screenshot exists.
|
|
200
|
+
- Right column, top to bottom:
|
|
201
|
+
- exception class (small, red)
|
|
202
|
+
- message
|
|
203
|
+
- `Failed at <button>file:line</button>` with `title={appFrame}`, plus `· {durationMs} ms` and `· {browser}` when present. The button is a plain `<span>` when `resolveEditorTarget` returns null.
|
|
204
|
+
- a "Stack trace" toggle, closed by default. It renders segments as follows:
|
|
205
|
+
- project lines: highlighted (`bg-yellow-100 font-semibold`)
|
|
206
|
+
- folded runs: a "… N more" button that expands to the lines in `text-neutral-mid`
|
|
207
|
+
- Test IDs:
|
|
208
|
+
- `failure-details`
|
|
209
|
+
- `failure-location`
|
|
210
|
+
- `stack-trace-toggle`
|
|
211
|
+
- `stack-frame-project`
|
|
212
|
+
- `stack-frames-folded`
|
|
213
|
+
|
|
214
|
+
- [ ] **Step 1: Write failing `FailureDetails` tests.**
|
|
215
|
+
- It renders the message and exception class.
|
|
216
|
+
- The `failure-location` text is `spec/a_spec.rb:12` and its title is the full `app_frame`.
|
|
217
|
+
- Clicking it (with the `readFile` mock returning success) calls `navigate('/file-editor', {state:{fileName:'a_spec.rb', filePath:'/p/spec/a_spec.rb', line:12}})`.
|
|
218
|
+
- With `readFile` failing, it is not a button.
|
|
219
|
+
- Without `appFrame` and with an empty backtrace, there is no `failure-location` and no `stack-trace-toggle`.
|
|
220
|
+
- The trace is closed initially. After the toggle, there is 1 `stack-frame-project`, and a `stack-frames-folded` reading "… 2 more".
|
|
221
|
+
- Without a screenshot, no `img` is rendered.
|
|
222
|
+
- [ ] **Step 2: Extend `ProjectDashboard.test.tsx`.** A failed raider result renders `failure-details`, and a passed one does not.
|
|
223
|
+
- [ ] **Step 3: Run them.** Expected: FAIL.
|
|
224
|
+
- [ ] **Step 4: Implement.**
|
|
225
|
+
- [ ] **Step 5: Run** vitest, typecheck and eslint. Then run `npx electron-vite build && npx playwright test e2e/failure-video.spec.ts e2e/dashboard-page.spec.ts`. Expected: green. The failure-video spec's selectors must still pass.
|
|
226
|
+
- [ ] **Step 6: Commit:** "Show failures as a side-by-side card with a foldable stack trace".
|
|
227
|
+
|
|
228
|
+
### Task 5: SaaS — store `app_frame` per result
|
|
229
|
+
|
|
230
|
+
**Files:**
|
|
231
|
+
- Create: `db/migrate/<timestamp>_add_app_frame_to_test_results.rb`, which adds `t.string :app_frame, limit: 500`, nullable
|
|
232
|
+
- Modify: `db/schema.rb` (regenerated), `app/services/ingest.rb` (`insert_results`), `app/presenters/api/v1/build_presenter.rb` (`failure`)
|
|
233
|
+
- Test: the ingest spec under `spec/services`, `spec/requests/api` build specs, and `spec/requests/mcp_tools_spec.rb` if it asserts `app_frame`
|
|
234
|
+
|
|
235
|
+
**Interfaces:**
|
|
236
|
+
- `test_results.app_frame`: string or nil. It is written at ingest from `failure["app_frame"]` for failing statuses only, truncated with `.to_s.first(ErrorGroup::SAMPLE_APP_FRAME_LIMIT).presence`.
|
|
237
|
+
- `BuildPresenter#failure` sets `app_frame: result.app_frame.presence || Failures::Signature.fallback_frame(result.backtrace)`. The OpenAPI shape is unchanged.
|
|
238
|
+
|
|
239
|
+
- [ ] **Step 1: Write failing specs.**
|
|
240
|
+
- Ingesting a failed result with `app_frame: "spec/a_spec.rb:12:in 'x'"` stores it.
|
|
241
|
+
- A 600-character `app_frame` is stored truncated to 500.
|
|
242
|
+
- A passed result stores nil.
|
|
243
|
+
- The API build detail returns the stored `app_frame` when present, and the `fallback_frame` value when the column is nil. The response is still valid against `docs/openapi.yaml`.
|
|
244
|
+
- [ ] **Step 2: Run them.** Expected: FAIL.
|
|
245
|
+
- [ ] **Step 3: Implement.** Add the migration, run `bin/rails db:migrate`, then add the ingest and presenter changes.
|
|
246
|
+
- [ ] **Step 4: Run** the full suite on SQLite and on Postgres, plus `bin/rubocop`. Expected: green.
|
|
247
|
+
- [ ] **Step 5: Commit:** "Store app_frame per test result and prefer it in the API".
|
|
248
|
+
|
|
249
|
+
### Task 6: SaaS — the failure card (layout B)
|
|
250
|
+
|
|
251
|
+
**Files:**
|
|
252
|
+
- Create: `app/helpers/stack_trace_helper.rb`
|
|
253
|
+
- Modify: `app/views/test_results/_card.html.erb`, splitting into a partial `app/views/test_results/_failure.html.erb` if that is clearer
|
|
254
|
+
- Test: `spec/helpers/stack_trace_helper_spec.rb`, `spec/requests/run_detail_spec.rb`
|
|
255
|
+
|
|
256
|
+
**Interfaces:**
|
|
257
|
+
- Consumes: from Task 5, `TestResult#app_frame`.
|
|
258
|
+
- Produces in `StackTraceHelper`:
|
|
259
|
+
- `parse_frame(str) -> { file:, line: } | nil`
|
|
260
|
+
- `project_frame?(line, app_frame) -> Boolean`
|
|
261
|
+
- `fold_frames(lines, app_frame) -> Array<Hash>`, where each segment is `{ kind: :project, text: }` or `{ kind: :folded, lines: [] }`
|
|
262
|
+
- `failure_location(app_frame) -> "file:line" | nil`
|
|
263
|
+
- All follow the Global Constraints rules. Backtrace lines come from `result.backtrace.to_s.split("\n")`.
|
|
264
|
+
|
|
265
|
+
**Card changes:**
|
|
266
|
+
- Keep the `<details>` summary row unchanged.
|
|
267
|
+
- For failing results, the open body becomes layout B:
|
|
268
|
+
- **Left:** the screenshot `<img>` (named `screenshot`, else the first image), still via `attachment_path` and `lightbox#open`, rendered only when attachments exist and haven't expired.
|
|
269
|
+
- **Right:**
|
|
270
|
+
- the exception class and message box, as today
|
|
271
|
+
- `<p data-testid="failure-location" title="<full app_frame>">Failed at <code>file:line</code> · browser</p>`, with the browser from `result.run.environment["browser"]` when present
|
|
272
|
+
- `<details data-testid="stack-trace">`, with the summary "Stack trace". Project lines go in `<span data-testid="stack-frame-project">` with a highlight class. Each folded run is a nested `<details data-testid="stack-frames-folded"><summary>… N more</summary>…dimmed lines…</details>`.
|
|
273
|
+
- **Below both columns:**
|
|
274
|
+
- `<details>` "Failure video" around any video attachment
|
|
275
|
+
- other attachments listed as today
|
|
276
|
+
- the existing expired and purged notes
|
|
277
|
+
- Non-failing results render as today.
|
|
278
|
+
- No JavaScript and no inline styles.
|
|
279
|
+
|
|
280
|
+
- [ ] **Step 1: Write a failing helper spec.**
|
|
281
|
+
- The same cases as Task 2 Step 1, in Ruby.
|
|
282
|
+
- `failure_location("spec/a_spec.rb:12:in 'x'")` returns `"spec/a_spec.rb:12"`.
|
|
283
|
+
- `failure_location(nil)` returns `nil`.
|
|
284
|
+
- [ ] **Step 2: Write failing request specs in `run_detail_spec.rb`.**
|
|
285
|
+
- A failing result with an `app_frame` and a screenshot attachment renders `[data-testid=failure-location]` with the text `spec/a_spec.rb:12` and the screenshot `img`.
|
|
286
|
+
- A failing result without attachments renders no `img` and still shows the message.
|
|
287
|
+
- A failing result whose attachments are expired shows `attachments-expired` and no `img`.
|
|
288
|
+
- A failing result with no `app_frame` and no backtrace has no `failure-location` and no `stack-trace`.
|
|
289
|
+
- An all-gem backtrace of 7 lines renders one `stack-frames-folded` with "… 7 more".
|
|
290
|
+
- A run with `environment: {"browser" => "chrome"}` shows "chrome".
|
|
291
|
+
- A passed result renders no `failure-location`.
|
|
292
|
+
- [ ] **Step 3: Run them.** Expected: FAIL.
|
|
293
|
+
- [ ] **Step 4: Implement.**
|
|
294
|
+
- [ ] **Step 5: Run** the full suite on SQLite and Postgres (including `no_inline_handlers_spec`), plus `bin/rubocop`. Expected: green. Also render one run page with a failing result in a local server on port 3002 and screenshot it for the review.
|
|
295
|
+
- [ ] **Step 6: Commit:** "Show failures as a side-by-side card with a foldable stack trace".
|