kwikqa 0.0.0-stage → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,121 @@
1
+ KwikQA End User License Agreement
2
+
3
+ Effective 21 October 2026
4
+ Copyright (c) 2026 KwikQA. All rights reserved.
5
+
6
+ KwikQA is proprietary software. It is not open source, and this agreement does not grant any
7
+ open-source license to it.
8
+
9
+ This agreement is between you and KwikQA ("we"). It governs the KwikQA command-line software,
10
+ including the reporter and failure-capture files it installs into your test projects (together,
11
+ the "Software"), and your use of the KwikQA hosted service the Software connects to (the
12
+ "Service"). The Terms of Service at https://kwikqa.com/terms/ also apply to the Service, and the
13
+ Privacy Policy at https://kwikqa.com/privacy/ explains how personal data is handled. If this
14
+ agreement and the Terms of Service conflict about the Software, this agreement governs.
15
+
16
+ By installing, copying, or using the Software you accept this agreement. If you use it for an
17
+ organization, you confirm that you may accept it for that organization, and "you" includes it. If
18
+ you do not accept, do not install or use the Software.
19
+
20
+ 1. License. Subject to this agreement and to the plan you hold, we grant you a limited,
21
+ non-exclusive, non-transferable, non-sublicensable, revocable license to install and run the
22
+ Software, on machines and in continuous-integration environments you control, to analyze and
23
+ repair the test suites of software that you or your organization develop. The reporter and
24
+ capture files may be kept in your test projects and run as part of your own test runs, only for
25
+ that purpose.
26
+
27
+ 2. Restrictions. You may not, and may not allow anyone else to:
28
+ (a) copy, modify, merge, or distribute the Software, except to install and run it under this
29
+ license and to keep the files described in section 1 in your projects;
30
+ (b) decompile, disassemble, or reverse engineer the Software, except to the extent applicable
31
+ law permits this despite this restriction;
32
+ (c) sell, rent, lease, lend, sublicense, or provide the Software or the Service to third parties
33
+ as a hosted or managed service;
34
+ (d) use the Software or the Service to build or offer a competing product or service;
35
+ (e) circumvent plan limits, usage metering, authentication, request approval, or any other
36
+ technical control, including by changing the Software so that it does not report usage;
37
+ (f) remove or alter any proprietary, copyright, or license notice; or
38
+ (g) use the Software or the Service in violation of applicable law, including export control
39
+ and sanctions laws.
40
+
41
+ 3. Third-party components. The Software includes open-source components, each licensed under its
42
+ own license. They are listed, with their license texts, in THIRD_PARTY_NOTICES.md, which is
43
+ distributed with the Software. Those licenses govern those components, and nothing in this
44
+ agreement limits your rights under them.
45
+
46
+ 4. Plans, fees, and refunds. Plans, prices, and monthly limits are described at
47
+ https://kwikqa.com/pricing/. Usage is counted per AI request as described there. Paid plans are
48
+ sold by Paddle.com, our merchant of record, renew monthly or yearly until cancelled, and are
49
+ refunded as described at https://kwikqa.com/refunds/, including a full refund of your first
50
+ payment if you cancel within 14 days of it. We may change plans or prices with at least 30
51
+ days' notice; a change applies from your next billing period after the notice ends.
52
+
53
+ 5. Your data. You keep all rights in your code, test reports, evidence, and the changes the
54
+ Software makes to your code. The Software builds a redacted, size-limited evidence packet on
55
+ your machine and shows you a preview of each AI request before anything is sent.
56
+ (a) On hosted plans, an approved packet goes to the Service, which checks it, counts it toward
57
+ your plan, and forwards it unchanged to OpenRouter, which sends it to the model endpoint
58
+ named in the preview. The Service requires that every such request is routed only to an
59
+ endpoint with a zero-data-retention policy. The Service stores account, plan, usage, and
60
+ request metadata and a hash of each request; it does not store the evidence, your source
61
+ code, or the model's answer, which it holds only in memory until your Software collects it
62
+ (at most 15 minutes).
63
+ (b) On bring-your-own-key plans, requests go from your machine directly to OpenRouter under your
64
+ own OpenRouter account and its terms, and you are responsible for those charges. The
65
+ Software reports each request's kind, model, provider, and test framework to the Service so
66
+ it counts toward your plan.
67
+ (c) If your team's owner turns on team insights, the Software also shares the names, files, and
68
+ projects of failing and flaky tests and the outcome of each fix with your team.
69
+ (d) Run records, patches, transaction journals, captures, and validation output stay on your
70
+ machine.
71
+ You are responsible for making sure that what you approve sending does not include data you
72
+ are not permitted to share. https://kwikqa.com/security/ describes this in detail.
73
+
74
+ 6. AI output. Diagnoses and proposed repairs are generated with AI models and may be incomplete or
75
+ wrong. The Software checks proposals against its repair rules, applies a change only after you
76
+ approve it, and validates applied changes by running your tests, but none of this guarantees
77
+ that a diagnosis is correct or that a change is suitable for your code. You are responsible for
78
+ reviewing and accepting changes to your code and for how you use the output.
79
+
80
+ 7. Ownership and feedback. The Software and the Service, and all intellectual property in them,
81
+ belong to KwikQA and its licensors. This agreement grants no rights except those stated in it.
82
+ If you send us suggestions or feedback, we may use them without obligation to you.
83
+
84
+ 8. Updates. We may update the Software and the Service, and may stop supporting older versions of
85
+ the Software when the Service changes. This agreement applies to every update unless an update
86
+ comes with a different agreement.
87
+
88
+ 9. Term and termination. This agreement lasts until it is terminated. It ends automatically if you
89
+ breach it, and you may end it at any time by deleting the Software. When it ends you must stop
90
+ using the Software and delete it. Sections 2, 3, 5, 6, 7, 9, 10, 11, 12, and 13 survive
91
+ termination.
92
+
93
+ 10. No warranty. TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE AND THE SERVICE ARE PROVIDED
94
+ "AS IS" AND "AS AVAILABLE", WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED, OR STATUTORY,
95
+ INCLUDING ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, OR
96
+ NON-INFRINGEMENT, AND ANY WARRANTY THAT THE SOFTWARE OR THE SERVICE WILL BE UNINTERRUPTED OR
97
+ ERROR-FREE, OR THAT ANY DIAGNOSIS OR REPAIR WILL BE CORRECT OR WILL MAKE A TEST PASS.
98
+
99
+ 11. Limitation of liability. TO THE MAXIMUM EXTENT PERMITTED BY LAW: (a) KWIKQA IS NOT LIABLE FOR
100
+ ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, OR FOR ANY
101
+ LOSS OF DATA, PROFITS, REVENUE, OR GOODWILL, HOWEVER CAUSED, EVEN IF ADVISED OF THEIR
102
+ POSSIBILITY; AND (b) KWIKQA'S TOTAL LIABILITY FOR ALL CLAIMS ARISING OUT OF OR RELATING TO THIS
103
+ AGREEMENT, THE SOFTWARE, OR THE SERVICE IS LIMITED TO THE GREATER OF THE AMOUNT YOU PAID FOR THE
104
+ SERVICE IN THE TWELVE MONTHS BEFORE THE EVENT GIVING RISE TO THE CLAIM AND ONE HUNDRED US
105
+ DOLLARS (USD 100). NOTHING IN THIS AGREEMENT LIMITS LIABILITY THAT CANNOT BE LIMITED UNDER
106
+ APPLICABLE LAW, OR ANY RIGHTS YOU HAVE AS A CONSUMER THAT CANNOT BE WAIVED.
107
+
108
+ 12. Governing law. This agreement is governed by the laws of India, without regard to its
109
+ conflict-of-laws rules. The courts of India have jurisdiction over disputes arising from it,
110
+ except that we may seek urgent relief to protect our intellectual property in any competent
111
+ court. If you are a consumer, this does not take away the protection of the mandatory laws of
112
+ the country where you live.
113
+
114
+ 13. General. This agreement, together with the Terms of Service, is the entire agreement about the
115
+ Software and supersedes any prior agreement about it. You may not assign it without our
116
+ consent; we may assign it to a successor of our business. If a provision is unenforceable, the
117
+ rest remains in effect. Not enforcing a provision is not a waiver of it. We may update this
118
+ agreement; the version in effect when you install or update the Software applies to that
119
+ version, and material changes are announced on https://kwikqa.com.
120
+
121
+ Contact: support@kwikqa.com (help), security@kwikqa.com (security), privacy@kwikqa.com (privacy).
package/README.md CHANGED
@@ -1,3 +1,220 @@
1
- # Temporary Holding Version
1
+ # KwikQA
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ KwikQA reads a failed **Playwright**, **Cypress**, or **WebdriverIO** run and tells you why it
4
+ failed, using the evidence in your report, the page at the moment of failure, and your test source.
5
+ When the test itself is at fault, it proposes a small repair to your test code, shows you the exact
6
+ diff, applies it only after you approve, and keeps it only if your tests then pass.
7
+
8
+ ```sh
9
+ npm i -D kwikqa
10
+ npx playwright test # or: npx cypress run, npx wdio run wdio.conf.ts
11
+ npx kwikqa analyze
12
+ npx kwikqa fix
13
+ npx kwikqa validate
14
+ ```
15
+
16
+ ## What it will and won't do
17
+
18
+ - **Diagnoses with evidence.** Every cause it reports cites the report fields, trace events, or source
19
+ lines that support it, and it says so when the evidence isn't enough.
20
+ - **Repairs only test code.** It edits specs, page objects, fixtures, and helpers that your
21
+ `kwikqa.config.json` allows. It never edits application code, dependencies, or lockfiles.
22
+ - **Never weakens a test.** It refuses edits that skip or focus tests, add fixed waits
23
+ (`waitForTimeout`, `cy.wait(1000)`, `browser.pause`) or retries, force actions past Cypress's
24
+ checks, remove or swap assertions and `should` chainers, or swallow errors.
25
+ - **Nothing happens without you.** You see each AI request and each patch before it happens. Applying a
26
+ patch always asks, even in CI mode.
27
+ - **Keeps a repair only if it validates.** `validate` reruns the failing test in a fresh process with
28
+ your project's own Playwright, Cypress, or WebdriverIO. Cypress runs the test's spec with every
29
+ other test skipped except the ones that passed before, which must still pass. If anything required
30
+ fails, the patch is rolled back. One retry is allowed, then it stops.
31
+ - **Leaves your other work alone.** It won't touch files you have already modified, and rollback
32
+ restores only the bytes it changed.
33
+
34
+ ## Requirements
35
+
36
+ - Node.js 20.11 or later
37
+ - Playwright Test, Cypress, or WebdriverIO (Mocha or Jasmine) installed in your project. KwikQA
38
+ runs your project's own copy and never installs packages or browsers.
39
+ - A git repository
40
+
41
+ ## Setup
42
+
43
+ ```sh
44
+ npm i -D kwikqa
45
+ npx kwikqa init
46
+ npx kwikqa login
47
+ ```
48
+
49
+ `init` proposes a `kwikqa.config.json` that lists which files KwikQA may edit, and offers to add
50
+ `.kwikqa/` (local run records) to `.gitignore`. Review and commit the config like any other code.
51
+
52
+ Then point KwikQA at your runs:
53
+
54
+ **Playwright.** Add a JSON reporter to `playwright.config.ts`, and keep traces for failures:
55
+
56
+ ```ts
57
+ export default defineConfig({
58
+ reporter: [["list"], ["json", { outputFile: "test-results/kwikqa-report.json" }]],
59
+ use: { trace: "retain-on-failure" },
60
+ });
61
+ ```
62
+
63
+ **Cypress.** Add KwikQA's reporter to `cypress.config.ts`. It keeps every retry attempt, where each
64
+ test is declared, and each error's code frame:
65
+
66
+ ```ts
67
+ export default defineConfig({
68
+ reporter: "node_modules/kwikqa/dist/cypress-reporter.cjs",
69
+ // ...
70
+ });
71
+ ```
72
+
73
+ Already writing mochawesome or JUnit reports? KwikQA reads those too:
74
+ `npx kwikqa analyze --report mochawesome-report`.
75
+
76
+ **WebdriverIO.** Add KwikQA's reporter to `wdio.conf.ts`, next to your own:
77
+
78
+ ```ts
79
+ import KwikqaReporter from "kwikqa/wdio-reporter";
80
+
81
+ export const config = {
82
+ reporters: ["spec", [KwikqaReporter, {}]],
83
+ // ...
84
+ };
85
+ ```
86
+
87
+ `@wdio/json-reporter` and JUnit reports work too: `npx kwikqa analyze --report <directory>`.
88
+
89
+ KwikQA's reporters write to `test-results/kwikqa/`, which `analyze` finds on its own. Cypress and
90
+ WebdriverIO keep no trace of a failure; run `npx kwikqa analyze --capture` and KwikQA re-runs the
91
+ failing specs and records each failure's page (see below).
92
+
93
+ `login` signs you in with GitHub; the free plan needs no payment details.
94
+
95
+ ## Commands
96
+
97
+ | Command | What it does |
98
+ | --- | --- |
99
+ | `npx kwikqa analyze` | Diagnose the failures in the latest report. `--report <path>` picks a report file or a directory of one run's reports; `--framework` names the framework of a JUnit report; `--local` builds the evidence summary without sending anything; `--no-changes` leaves out recent code changes (see below). |
100
+ | `npx kwikqa analyze --capture` | First re-run the failing tests so every failure has full evidence. Playwright runs with full tracing; with Playwright 1.59+ and [Playwright CLI](https://www.npmjs.com/package/@playwright/cli) installed, it also pauses locator failures at the failing line and asks Playwright for locators of the closest elements (`--no-probe` skips this). Cypress and WebdriverIO re-run the failing specs with your own config plus KwikQA's reporter and failure capture, which records each failed attempt's DOM, command log, console, and network. Asks before running anything; `--yes` approves in CI. |
101
+ | `npx kwikqa fix` | Propose a repair for a test-side cause, show the diff and validation commands, and apply it if you approve. First it offers to re-run the failing tests once: a test that passes now is flaky, so KwikQA reports it and requests no repair. It starts with the failure group whose test-side diagnosis is strongest; if the model declines, or its proposal fails KwikQA's checks, it asks about the next such group, at most three, each with its own preview. One `fix` counts as one fix however many groups it tries. `--group <n>` repairs only group n, as `analyze` numbered it. `--propose-only` stops after the proposal. |
102
+ | `npx kwikqa validate` | Run the approved validation. The repaired test must pass three times in a row, with retries off. Passing keeps the patch; failing rolls it back. When other tests failed with the same cause, it then offers to re-run them once and names any that still fail, so you can run `fix` again for them; the patch stays either way. |
103
+ | `npx kwikqa fix --retry` | After a failed validation, re-diagnose from the new evidence and propose one revised patch. |
104
+ | `npx kwikqa status` | Show your plan, team, and this month's usage. |
105
+ | `npx kwikqa upgrade [plan]` | Show plans, or get a checkout link for one. `--annual` bills yearly; `--seats <n>` sets seats on team plans. On a paid plan it points you to Billing in the dashboard. While the paid plans are coming soon, it lists them without a link. |
106
+ | `npx kwikqa billing` | Open Paddle's customer portal to update your payment method or download invoices. |
107
+ | `npx kwikqa init` / `login` / `logout` | Set up the project, sign in, sign out (`logout` also revokes the token). |
108
+
109
+ Exit codes: `0` success or an informative result, `1` the repair or validation did not succeed, `2`
110
+ usage error, `3` sign-in, plan, or usage-limit problem.
111
+
112
+ ## Plans
113
+
114
+ | Plan | Monthly | Annual | Analyses / month | Fixes / month |
115
+ | --- | --- | --- | --- | --- |
116
+ | Free | $0 | – | 10 | 3 |
117
+ | Pro | $20 | $200 | 90 | 20 |
118
+ | Team (3+ seats) | $25 / seat | $250 / seat | 100 / seat, shared | 25 / seat, shared |
119
+ | BYOK | $10 | $100 | 500 | 150 |
120
+ | BYOK Team (3+ seats) | $15 / seat | $150 / seat | Unlimited | Unlimited |
121
+
122
+ One analysis is one diagnosis sent to the AI. One fix is one repair attempt, validated by
123
+ `npx kwikqa validate`, and its one retry is included. Local-only analysis is free, and so is any
124
+ request you decline or that stops before sending. `fix` reuses the diagnosis `analyze` already
125
+ produced for the same run, so it doesn't use another analysis.
126
+
127
+ The paid plans are coming soon: until they open, every account uses Free.
128
+
129
+ Annual billing gives two months free. Cancel within 14 days of your first payment, monthly or
130
+ annual, and it is refunded in full. After that, cancelling an annual plan from the dashboard refunds
131
+ its unused months; monthly plans stay active until the end of the paid month. Payments go through
132
+ Paddle, the merchant of record.
133
+
134
+ ### Teams
135
+
136
+ On Team and BYOK Team, the owner invites people by GitHub username at
137
+ https://kwikqa.com/dashboard/team/. Everyone, including CI tokens they create, draws from one
138
+ monthly pool.
139
+
140
+ The owner can also turn on **team insights**: the flakiest tests, fix history, acceptance rate,
141
+ and repair success at https://kwikqa.com/dashboard/insights/. While insights are on, the CLI shares
142
+ the names, files, and projects of failing and flaky tests and each fix's outcome with the team, and
143
+ says so after each command. It never shares evidence, source, diffs, or error messages, and
144
+ `analyze --local` shares nothing. `npx kwikqa status` shows whether insights are on.
145
+
146
+ ### Bring your own key
147
+
148
+ On the BYOK plans, KwikQA sends requests straight to OpenRouter with your key, and your evidence
149
+ never passes through KwikQA's servers. The CLI reports each request's kind, model, and provider to
150
+ KwikQA so it counts toward your plan:
151
+
152
+ ```sh
153
+ export OPENROUTER_API_KEY=sk-or-...
154
+ # Optional: choose your own models as author/model@provider
155
+ export KWIKQA_DIAGNOSIS_MODEL=anthropic/claude-haiku-5.5@anthropic
156
+ export KWIKQA_REPAIR_MODEL=openai/gpt-6.1-sol@openai
157
+ ```
158
+
159
+ ## What is sent, and where
160
+
161
+ Before any request, KwikQA builds a bounded evidence packet on your machine from your failing tests
162
+ only. It contains selected report fields, trace events or the Cypress and WebdriverIO command log,
163
+ the elements around each failed locator, the accessibility snapshot of the page at the failure,
164
+ short source excerpts, and recent code changes that mention the failure. Secrets,
165
+ credentials, password fields, and sensitive fields are removed from that packet before you see the
166
+ preview. Redaction recognizes credentials, not every kind of personal data: names or email
167
+ addresses in your test data can still appear in page text, so test with data you may share.
168
+ Screenshots and videos are described by size and dimensions only; their pixels are never sent. Full
169
+ files, full diffs, and your whole repository are never sent.
170
+
171
+ Recent code changes tell a deliberately changed app from a broken one. KwikQA searches your
172
+ uncommitted changes and your branch's commits since it left the default branch (on the default
173
+ branch, the last 20 commits from the past 30 days). It looks for changed lines that contain what
174
+ the failure names: locator text and test ids, expected and received values, and failed request
175
+ paths. Only matching hunks of source files are kept, at most 12, each with its commit's subject
176
+ line. The subjects of the commits searched (at most 20) and the names of source files with
177
+ uncommitted changes are listed too. Author names, emails, commit bodies, and documentation are
178
+ never read. The preview says how
179
+ many changed hunks a request carries. `--no-changes` on `analyze` and `fix` leaves them out.
180
+
181
+ - **Free, Pro, Team:** the redacted packet goes to the KwikQA service, which forwards it unchanged to
182
+ OpenRouter and the model endpoint shown in the preview. Requests go only to endpoints with a
183
+ zero-data-retention policy (Claude on Google Cloud Vertex AI, GPT on Microsoft Azure): the
184
+ service refuses a request that doesn't ask for that, and OpenRouter refuses rather than route it
185
+ to an endpoint that would keep it. The service keeps only account, usage, token, and cost
186
+ metadata and a hash of the request. It never stores your evidence, your source, or the model's
187
+ answer, which it holds in memory until your CLI collects it, for at most 15 minutes.
188
+ - **BYOK, BYOK Team:** the packet goes from your machine directly to OpenRouter, under your
189
+ OpenRouter account and its settings. KwikQA's default routes ask for zero data retention there
190
+ too; a route you set with `KWIKQA_DIAGNOSIS_MODEL` or `KWIKQA_REPAIR_MODEL` does not. The CLI
191
+ reports only the request's kind, model, provider, and test framework to KwikQA, to count it.
192
+
193
+ Run records, transaction journals, captures, and validation output stay on your machine in
194
+ `.kwikqa/`. [Security and data handling](https://kwikqa.com/security/) and the
195
+ [privacy policy](https://kwikqa.com/privacy/) have the details.
196
+
197
+ ## CI
198
+
199
+ `analyze` can run unattended; `fix` cannot, because applying a patch always needs a person.
200
+
201
+ ```sh
202
+ export KWIKQA_TOKEN=... # create a CI token at https://kwikqa.com/dashboard/account/
203
+ npx playwright test || npx kwikqa analyze --yes
204
+ npx cypress run || npx kwikqa analyze --yes
205
+ ```
206
+
207
+ ## Account and billing
208
+
209
+ The dashboard at https://kwikqa.com/dashboard/ shows your plan, usage, and recent requests. There
210
+ you can change your plan, seats, or billing period, cancel, manage your team, create and revoke
211
+ CLI and CI tokens, download your data, or delete your account.
212
+
213
+ ## Support and license
214
+
215
+ KwikQA is proprietary software licensed under the end user license agreement in the package's
216
+ `LICENSE` file; it is not open source. The open-source components bundled with it, and their
217
+ licenses, are listed in `dist/THIRD_PARTY_NOTICES.md` in the package. The hosted service is also
218
+ covered by the [terms of service](https://kwikqa.com/terms/), the
219
+ [privacy policy](https://kwikqa.com/privacy/), and the [refund policy](https://kwikqa.com/refunds/).
220
+ Questions and support: https://kwikqa.com/support
@@ -0,0 +1,54 @@
1
+ # Third-party notices
2
+
3
+ KwikQA is proprietary software (see LICENSE). It includes the following open-source
4
+ components, each licensed under its own license, reproduced here.
5
+
6
+ ## @babel/parser 7.29.9 (MIT)
7
+
8
+ ```text
9
+ Copyright (C) 2012-2014 by various contributors (see AUTHORS)
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in
19
+ all copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
+ THE SOFTWARE.
28
+ ```
29
+
30
+ ## fflate 0.8.3 (MIT)
31
+
32
+ ```text
33
+ MIT License
34
+
35
+ Copyright (c) 2026 Arjun Barrett
36
+
37
+ Permission is hereby granted, free of charge, to any person obtaining a copy
38
+ of this software and associated documentation files (the "Software"), to deal
39
+ in the Software without restriction, including without limitation the rights
40
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
41
+ copies of the Software, and to permit persons to whom the Software is
42
+ furnished to do so, subject to the following conditions:
43
+
44
+ The above copyright notice and this permission notice shall be included in all
45
+ copies or substantial portions of the Software.
46
+
47
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
48
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
49
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
50
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
51
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
52
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
53
+ SOFTWARE.
54
+ ```
@@ -0,0 +1,190 @@
1
+ // KwikQA's Cypress failure capture (KQA-081). `npx kwikqa analyze --capture` loads it through a
2
+ // generated support file that imports the project's own support file first, so custom commands
3
+ // and settings are unchanged. It never runs in a normal `cypress run`.
4
+ //
5
+ // When a test attempt fails it records, from the page under test: the DOM (as KwikQA's snapshot
6
+ // tree), the Cypress command log with each command's state and error, console errors and
7
+ // warnings, uncaught page errors, the network requests Cypress logged, and the paths of the
8
+ // failure screenshots. Each attempt is written with cy.writeFile under the capture directory set
9
+ // by the generated support file, named so KwikQA's reporter can attach it to the attempt.
10
+ /* global Cypress, cy, afterEach, beforeEach */
11
+
12
+ const settings = globalThis.__kwikqaCapture || {};
13
+ const OUT = settings.directory || ".kwikqa/capture/cypress";
14
+ // Validation runs only the tests a repair is accountable for: Cypress cannot select tests by name,
15
+ // so every other test is skipped before its own hooks run, and the run's exit code covers just these.
16
+ const ONLY = Array.isArray(settings.only) ? new Set(settings.only) : undefined;
17
+
18
+ if (ONLY) {
19
+ beforeEach(function kwikqaOnly() {
20
+ const test = this.currentTest;
21
+ if (test && !ONLY.has(test.titlePath().join(" "))) this.skip();
22
+ });
23
+ }
24
+ const MAX_NODES = 20000;
25
+ const MAX_TEXT = 2000;
26
+ const MAX_LOG = 300;
27
+ const SKIPPED_TAGS = new Set(["SCRIPT", "STYLE", "NOSCRIPT", "TEMPLATE", "LINK", "META"]);
28
+ const FORM_FIELDS = new Set(["INPUT", "TEXTAREA", "SELECT"]);
29
+
30
+ function fnv1a(text) {
31
+ let hash = 0x811c9dc5;
32
+ for (const byte of new TextEncoder().encode(text)) {
33
+ hash ^= byte;
34
+ hash = Math.imul(hash, 0x01000193) >>> 0;
35
+ }
36
+ return hash.toString(16).padStart(8, "0");
37
+ }
38
+
39
+ /** Must match captureKey in cypress-reporter.cjs. */
40
+ function captureKey(spec, titlePath, retry) {
41
+ const slug = String(spec)
42
+ .replace(/[^A-Za-z0-9]+/g, "_")
43
+ .slice(-60);
44
+ return `${slug}--${fnv1a(`${spec}\u0000${titlePath.join("\u0000")}`)}--${retry}`;
45
+ }
46
+
47
+ /** The page as `[TAG, {attributes}, ...children]`, text as strings: KwikQA's snapshot tree. */
48
+ function serialize(root) {
49
+ let budget = MAX_NODES;
50
+ const visit = (node) => {
51
+ if (budget <= 0) return undefined;
52
+ if (node.nodeType === 3) {
53
+ const value = node.textContent || "";
54
+ return value.trim() === "" ? undefined : value.slice(0, MAX_TEXT);
55
+ }
56
+ if (node.nodeType !== 1) return undefined;
57
+ if (SKIPPED_TAGS.has(node.tagName)) return undefined;
58
+ budget -= 1;
59
+ const attributes = {};
60
+ for (const attribute of Array.from(node.attributes)) {
61
+ attributes[attribute.name] = String(attribute.value).slice(0, MAX_TEXT);
62
+ }
63
+ // Form state is a property, not an attribute; the snapshot records what the user saw, under
64
+ // the names KwikQA's DOM renderer already reads from Playwright snapshots.
65
+ if (FORM_FIELDS.has(node.tagName) && typeof node.value === "string") {
66
+ attributes.__playwright_value_ = node.value.slice(0, MAX_TEXT);
67
+ }
68
+ if (node.checked === true) attributes.__playwright_checked_ = "true";
69
+ const children = [];
70
+ const childNodes = node.shadowRoot
71
+ ? [...Array.from(node.shadowRoot.childNodes), ...Array.from(node.childNodes)]
72
+ : Array.from(node.childNodes);
73
+ for (const child of childNodes) {
74
+ const value = visit(child);
75
+ if (value !== undefined) children.push(value);
76
+ }
77
+ return [node.tagName, attributes, ...children];
78
+ };
79
+ return { tree: visit(root), truncated: budget <= 0 };
80
+ }
81
+
82
+ let commands = [];
83
+ let consoleEntries = [];
84
+ let screenshots = [];
85
+
86
+ function brief(attributes) {
87
+ const props = attributes.renderProps || {};
88
+ const message = props.message !== undefined ? props.message : attributes.message;
89
+ return {
90
+ id: attributes.id,
91
+ name: String(attributes.name || ""),
92
+ ...(attributes.displayName ? { displayName: String(attributes.displayName) } : {}),
93
+ message: String(message === undefined ? "" : message).slice(0, MAX_TEXT),
94
+ state: String(attributes.state || ""),
95
+ ...(attributes.err
96
+ ? { error: String(attributes.err.message || attributes.err).slice(0, MAX_TEXT) }
97
+ : {}),
98
+ ...(attributes.url ? { url: String(attributes.url) } : {}),
99
+ };
100
+ }
101
+
102
+ Cypress.on("test:before:run", () => {
103
+ commands = [];
104
+ consoleEntries = [];
105
+ screenshots = [];
106
+ });
107
+ Cypress.on("log:added", (attributes) => {
108
+ if (commands.length < MAX_LOG) commands.push(brief(attributes));
109
+ });
110
+ Cypress.on("log:changed", (attributes) => {
111
+ const index = commands.findIndex((entry) => entry.id === attributes.id);
112
+ if (index >= 0) commands[index] = brief(attributes);
113
+ });
114
+ Cypress.on("window:before:load", (win) => {
115
+ for (const level of ["error", "warn"]) {
116
+ const original = win.console[level];
117
+ win.console[level] = (...args) => {
118
+ if (consoleEntries.length < MAX_LOG) {
119
+ consoleEntries.push({
120
+ level,
121
+ text: args
122
+ .map((arg) => String(arg))
123
+ .join(" ")
124
+ .slice(0, MAX_TEXT),
125
+ });
126
+ }
127
+ return original.apply(win.console, args);
128
+ };
129
+ }
130
+ win.addEventListener("error", (event) => {
131
+ consoleEntries.push({ level: "pageerror", text: String(event.message).slice(0, MAX_TEXT) });
132
+ });
133
+ win.addEventListener("unhandledrejection", (event) => {
134
+ consoleEntries.push({
135
+ level: "pageerror",
136
+ text: `Unhandled rejection: ${String(event.reason).slice(0, MAX_TEXT)}`,
137
+ });
138
+ });
139
+ });
140
+ Cypress.Screenshot.defaults({
141
+ onAfterScreenshot(_element, props) {
142
+ screenshots.push({ path: props.path, testFailure: props.testFailure === true });
143
+ },
144
+ });
145
+
146
+ afterEach(function kwikqaCapture() {
147
+ const test = this.currentTest;
148
+ if (test?.state !== "failed") return;
149
+ const titlePath = test.titlePath();
150
+ const retry =
151
+ typeof test.currentRetry === "function" ? test.currentRetry() : test._currentRetry || 0;
152
+ const spec = Cypress.spec.relative;
153
+ cy.document({ log: false }).then((doc) => {
154
+ const snapshot = serialize(doc.documentElement);
155
+ const network = commands
156
+ .filter(
157
+ (entry) =>
158
+ entry.name === "request" ||
159
+ entry.name === "xhr" ||
160
+ entry.displayName === "fetch" ||
161
+ entry.displayName === "xhr",
162
+ )
163
+ .map((entry) => ({
164
+ message: entry.message,
165
+ state: entry.state,
166
+ ...(entry.url ? { url: entry.url } : {}),
167
+ }));
168
+ const capture = {
169
+ schema: "kwikqa-capture",
170
+ schemaVersion: "1.0",
171
+ framework: "cypress",
172
+ spec,
173
+ titlePath,
174
+ retry,
175
+ url: doc.location ? doc.location.href : "",
176
+ title: doc.title,
177
+ viewport: {
178
+ width: Cypress.config("viewportWidth"),
179
+ height: Cypress.config("viewportHeight"),
180
+ },
181
+ dom: snapshot.tree,
182
+ domTruncated: snapshot.truncated,
183
+ commands: commands.map(({ id, ...rest }) => rest),
184
+ console: consoleEntries,
185
+ network,
186
+ screenshots,
187
+ };
188
+ cy.writeFile(`${OUT}/${captureKey(spec, titlePath, retry)}.json`, capture, { log: false });
189
+ });
190
+ });