squally-reporter 0.3.0 → 0.5.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.
Files changed (4) hide show
  1. package/README.md +182 -121
  2. package/dist/index.d.ts +95 -4
  3. package/dist/index.js +940 -115
  4. package/package.json +41 -38
package/README.md CHANGED
@@ -1,121 +1,182 @@
1
- # squally-reporter
2
-
3
- Playwright reporter that sends test results to your Squally dashboard.
4
-
5
- Learn more at [squally-app.vercel.app](https://squally-app.vercel.app).
6
-
7
- ## Requirements
8
-
9
- - Node.js >= 22.19.0 (required by the `undici` dependency)
10
- - `@playwright/test` >= 1.30.0
11
-
12
- ## Install
13
-
14
- ```bash
15
- npm install squally-reporter
16
- ```
17
-
18
- ## Config
19
-
20
- Set two env vars (e.g. in CI secrets or `.env`):
21
-
22
- ```bash
23
- SQUALLY_API_URL=https://squally-app.vercel.app
24
- SQUALLY_API_KEY=your-project-api-key
25
- ```
26
-
27
- Find your API key on your project's Settings page in the Squally dashboard.
28
-
29
- Then add the reporter to `playwright.config.ts`:
30
-
31
- ```ts
32
- import { defineConfig } from "@playwright/test";
33
-
34
- export default defineConfig({
35
- reporter: [
36
- ["list"],
37
- ["squally-reporter"],
38
- ],
39
- });
40
- ```
41
-
42
- Alternatively, pass config explicitly instead of using env vars:
43
-
44
- ```ts
45
- reporter: [
46
- ["squally-reporter", { apiUrl: "https://squally-app.vercel.app", apiKey: "..." }],
47
- ],
48
- ```
49
-
50
- That's it - run your tests as usual (`npx playwright test`) and results will
51
- be sent to Squally after the run finishes.
52
-
53
- ## Screenshot/trace upload
54
-
55
- If your `playwright.config.ts` captures screenshots or traces, the reporter
56
- uploads them to Squally so they show up next to the failing test in the
57
- dashboard - no separate setup beyond the two env vars above.
58
-
59
- **Only from attempts worth looking at:** screenshots/traces are uploaded
60
- only for attempts that **failed**, or that **needed a retry** (even if that
61
- retry then passed) - never for an attempt that passed on its first try,
62
- regardless of how `screenshot`/`trace` are configured. Nobody looks at a
63
- screenshot of a test that just passed, and uploading one anyway would only
64
- eat into your organization's storage for no benefit.
65
-
66
- **You need to actually enable capture in `playwright.config.ts`** - without
67
- it, Playwright doesn't produce any attachments, so there's nothing for the
68
- reporter to find or upload (no error either way, it's a silent no-op):
69
-
70
- ```ts
71
- export default defineConfig({
72
- use: {
73
- screenshot: "only-on-failure",
74
- trace: "retain-on-failure",
75
- },
76
- // ...
77
- });
78
- ```
79
-
80
- **Limits:**
81
- - 50 MB per individual file. Larger files are skipped (logged as a
82
- `console.warn`, the test result itself is still reported as usual).
83
- - A total storage cap per organization, depending on your Squally plan.
84
- Once reached, further uploads are skipped the same way until you either
85
- free up space or upgrade.
86
-
87
- Upload is best-effort and fully non-blocking, same principle as the rest of
88
- this reporter - a failed or skipped upload never fails your test run or
89
- changes its exit code, it only logs a `console.warn` explaining what
90
- happened.
91
-
92
- ## Tested revision (optional)
93
-
94
- If your test-automation code lives in the same repo as the application it
95
- tests, Squally can already tell which commit was tested from the CI
96
- provider's own commit env vars. If they're **separate repos** (e.g. a
97
- dedicated E2E-test repo pointed at a deployed app), set one of the following
98
- so Squally knows which application state was actually tested:
99
-
100
- ```bash
101
- # Same repo as the application under test - Squally will trust this CI
102
- # job's own commit (GITHUB_SHA / CI_COMMIT_SHA) as the tested revision.
103
- SAME_REPO_AS_APP=true
104
-
105
- # Separate repos - pass the tested revision explicitly (a commit SHA, tag,
106
- # or branch name/ref).
107
- TESTED_REVISION=v1.2.0
108
- ```
109
-
110
- Neither variable is required - without them, test results are still sent
111
- and stored exactly as before.
112
-
113
- ## Support
114
-
115
- Found a bug or have a question? Please open an issue on
116
- [GitHub Issues](https://github.com/squallydev-admin/squally-reporter/issues) -
117
- that's the primary way to report problems or ask questions.
118
-
119
- ## License
120
-
121
- MIT - see [LICENSE](./LICENSE).
1
+ # squally-reporter
2
+
3
+ Playwright reporter that sends test results to your Squally dashboard.
4
+
5
+ Learn more at [squally-app.vercel.app](https://squally-app.vercel.app).
6
+
7
+ ## Requirements
8
+
9
+ - Node.js >= 22.19.0 (required by the `undici` dependency)
10
+ - `@playwright/test` >= 1.30.0
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -D squally-reporter
16
+ ```
17
+
18
+ ## Config
19
+
20
+ Set two env vars (e.g. in CI secrets or `.env`):
21
+
22
+ ```bash
23
+ SQUALLY_API_URL=https://squally-app.vercel.app
24
+ SQUALLY_API_KEY=your-project-api-key
25
+ ```
26
+
27
+ Find your API key on your project's Settings page in the Squally dashboard.
28
+
29
+ Then add the reporter to `playwright.config.ts`:
30
+
31
+ ```ts
32
+ import { defineConfig } from "@playwright/test";
33
+
34
+ export default defineConfig({
35
+ reporter: [
36
+ ["list"],
37
+ ["squally-reporter"],
38
+ ],
39
+ });
40
+ ```
41
+
42
+ Alternatively, pass config explicitly instead of using env vars - **only if
43
+ `apiKey` itself comes from an env var or secret store at runtime, never as a
44
+ literal string in a committed file:**
45
+
46
+ ```ts
47
+ reporter: [
48
+ ["squally-reporter", { apiUrl: "https://squally-app.vercel.app", apiKey: process.env.SQUALLY_API_KEY }],
49
+ ],
50
+ ```
51
+
52
+ That's it - run your tests as usual (`npx playwright test`) and results will
53
+ be sent to Squally after the run finishes.
54
+
55
+ ## Screenshot/trace upload
56
+
57
+ If your `playwright.config.ts` captures screenshots or traces, the reporter
58
+ uploads them to Squally so they show up next to the failing test in the
59
+ dashboard - no separate setup beyond the two env vars above.
60
+
61
+ **Only from attempts worth looking at:** screenshots/traces are uploaded
62
+ only for attempts that **failed**, or that **needed a retry** (even if that
63
+ retry then passed) - never for an attempt that passed on its first try,
64
+ regardless of how `screenshot`/`trace` are configured. Nobody looks at a
65
+ screenshot of a test that just passed, and uploading one anyway would only
66
+ eat into your organization's storage for no benefit.
67
+
68
+ **You need to actually enable capture in `playwright.config.ts`** - without
69
+ it, Playwright doesn't produce any attachments, so there's nothing for the
70
+ reporter to find or upload (no error either way, it's a silent no-op):
71
+
72
+ ```ts
73
+ export default defineConfig({
74
+ use: {
75
+ screenshot: "only-on-failure",
76
+ trace: "retain-on-failure",
77
+ },
78
+ // ...
79
+ });
80
+ ```
81
+
82
+ **Limits:**
83
+ - 50 MB per individual file. Larger files are skipped (logged as a
84
+ `console.warn`, the test result itself is still reported as usual).
85
+ - A total storage cap per organization, depending on your Squally plan.
86
+ Once reached, further uploads are skipped the same way until you either
87
+ free up space or upgrade.
88
+ - Two minutes per upload, and 30 seconds for the calls that surround it. An
89
+ unreachable or very slow server means a skipped upload and a `console.warn`,
90
+ not a CI job waiting indefinitely.
91
+
92
+ Upload is best-effort and fully non-blocking, same principle as the rest of
93
+ this reporter - a failed or skipped upload never fails your test run or
94
+ changes its exit code, it only logs a `console.warn` explaining what
95
+ happened.
96
+
97
+ ## How results reach Squally
98
+
99
+ Results stream to Squally as your suite runs, not all at once at the end:
100
+ each test's result is sent in a small batch (at most every 25 results or 2
101
+ seconds, whichever comes first), so the "running" view in the dashboard stays
102
+ close to real time even for a long suite. There is no longer a single-request
103
+ size limit that could discard a whole run - a very large suite simply takes
104
+ more, smaller requests. As a courtesy the reporter still warns if a run's
105
+ result count climbs well past 5,000, since that usually means retries are
106
+ configured higher than intended rather than a suite that's actually that
107
+ large.
108
+
109
+ If Squally is unreachable partway through, results already sent stay in the
110
+ dashboard; only what could not be delivered is lost, and your test run's exit
111
+ code is never affected either way.
112
+
113
+ ## Sharding
114
+
115
+ **Handled automatically - no extra setup needed.** Each shard is still a
116
+ separate process with its own reporter instance, but Squally now correlates
117
+ them itself: all shards of one CI run are grouped together and shown as one
118
+ run once every shard has finished, with the flip side of that guarantee being
119
+ that the run only completes once every shard *has* reported.
120
+
121
+ Correlation is based on an id read from your CI provider's own environment,
122
+ so it costs you nothing on GitHub Actions, GitLab CI or CircleCI. If your
123
+ provider is not one of those (or you shard locally across several separate
124
+ `--shard` invocations), set `SQUALLY_CI_RUN_ID` to the same value for every
125
+ shard of one run - anything unique per CI run and shared by its shards works,
126
+ e.g. a build number your CI system already exposes.
127
+
128
+ **`playwright merge-reports` is not part of this and is not recommended** for
129
+ getting a single run in Squally - the automatic correlation above is the
130
+ supported path, and produces correct per-shard timing/branch/commit
131
+ information that a merge job cannot (see the historical note below). Nothing
132
+ stops you from using `merge-reports` for Playwright's own HTML report if you
133
+ want that separately; just don't point this reporter at the merged result
134
+ expecting Squally to treat it specially.
135
+
136
+ <details>
137
+ <summary>Why not just use `merge-reports`, historically</summary>
138
+
139
+ An earlier version of this reporter recommended `playwright merge-reports` as
140
+ the way to get one Squally run out of a sharded job. That path has a real
141
+ problem: branch, commit, commit message and author are all read on whichever
142
+ machine the reporter actually runs on - during a merge that is the merge job,
143
+ not the shards - and without a checkout there (and without the CI provider's
144
+ own commit variables) they all come out empty. A run with no branch is never
145
+ counted as a run on your stable branch, which is what Squally's broken/healthy
146
+ verdicts are based on - so a "merged" run could exist in the dashboard while
147
+ silently contributing nothing to flakiness detection. The automatic
148
+ correlation above reads that information from each shard's own environment
149
+ instead, so this problem does not arise.
150
+
151
+ </details>
152
+
153
+ ## Tested revision (optional)
154
+
155
+ If your test-automation code lives in the same repo as the application it
156
+ tests, Squally can already tell which commit was tested from the CI
157
+ provider's own commit env vars. If they're **separate repos** (e.g. a
158
+ dedicated E2E-test repo pointed at a deployed app), set one of the following
159
+ so Squally knows which application state was actually tested:
160
+
161
+ ```bash
162
+ # Same repo as the application under test - Squally will trust this CI
163
+ # job's own commit (GITHUB_SHA / CI_COMMIT_SHA) as the tested revision.
164
+ SAME_REPO_AS_APP=true
165
+
166
+ # Separate repos - pass the tested revision explicitly (a commit SHA, tag,
167
+ # or branch name/ref).
168
+ TESTED_REVISION=v1.2.0
169
+ ```
170
+
171
+ Neither variable is required - without them, test results are still sent
172
+ and stored exactly as before.
173
+
174
+ ## Support
175
+
176
+ Found a bug or have a question? Please open an issue on
177
+ [GitHub Issues](https://github.com/squallydev-admin/squally-reporter/issues) -
178
+ that's the primary way to report problems or ask questions.
179
+
180
+ ## License
181
+
182
+ MIT - see [LICENSE](./LICENSE).
package/dist/index.d.ts CHANGED
@@ -1,20 +1,111 @@
1
- import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult } from "@playwright/test/reporter";
1
+ import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestError, TestResult } from "@playwright/test/reporter";
2
2
  interface SquallyReporterOptions {
3
3
  apiUrl?: string;
4
4
  apiKey?: string;
5
5
  }
6
+ interface AppendErrorEntry {
7
+ message: string;
8
+ stack?: string;
9
+ snippet?: string;
10
+ location?: {
11
+ file: string;
12
+ line: number;
13
+ column: number;
14
+ };
15
+ }
16
+ interface AppendResultEntry {
17
+ testName: string;
18
+ filePath: string | null;
19
+ status: "passed" | "failed" | "skipped";
20
+ retryCount: number;
21
+ repeatIndex: number;
22
+ durationMs: number | null;
23
+ errors?: AppendErrorEntry[];
24
+ consoleOutput: string | null;
25
+ tags?: string[];
26
+ browserName: string | null;
27
+ errorContext: string | null;
28
+ }
29
+ type ArtifactType = "screenshot" | "trace";
30
+ declare function truncate(value: string, max: number): string;
31
+ declare function truncateOrNull(value: string | null, max: number): string | null;
32
+ declare function mapStatus(status: TestResult["status"]): AppendResultEntry["status"];
33
+ declare function detectCiRunId(env: NodeJS.ProcessEnv, generateFallback: () => string): string;
34
+ interface ProjectSuiteLike {
35
+ project(): {
36
+ name: string;
37
+ } | undefined;
38
+ }
39
+ declare function resolveProjectName(projectSuites: readonly ProjectSuiteLike[]): string | null;
40
+ declare function normalizeRevision(raw: string): string;
41
+ interface ResolvedRevision {
42
+ revision: string;
43
+ source: "ci_native" | "passed_param";
44
+ }
45
+ declare function resolveTestedRevision(): ResolvedRevision | null;
46
+ declare function toPosixPath(p: string): string;
47
+ declare function stripAnsi(value: string): string;
48
+ declare function maskSecrets(value: string): string;
49
+ declare function prepareConsoleOutput(value: string | null): string | null;
50
+ declare function mapErrors(errors: readonly TestError[]): AppendErrorEntry[] | undefined;
51
+ declare function attemptQualifiesForArtifacts(mappedStatus: AppendResultEntry["status"], retryCount: number): boolean;
52
+ declare function artifactKey(testName: string, filePath: string, retryCount: number, type: ArtifactType): string;
53
+ declare function parseRetryAfterMs(headerValue: string | null, nowMs: number): number | null;
54
+ declare function computeBackoffMs(retryNumber: number): number;
6
55
  export default class SquallyReporter implements Reporter {
7
56
  private apiUrl;
8
57
  private apiKey;
9
58
  private enabled;
10
59
  private startedAt;
11
- private results;
60
+ private dispatcher;
61
+ private ciRunId;
62
+ private shardIndex;
63
+ private shardTotal;
64
+ private projectName;
65
+ private runTags;
66
+ private runConfig;
67
+ private buffer;
12
68
  private artifacts;
69
+ private sendingDisabled;
70
+ private flushTimerHandle;
71
+ private inFlightFlushes;
72
+ private runIdPromise;
73
+ private runId;
13
74
  constructor(options?: SquallyReporterOptions);
14
- onBegin(_config: FullConfig, _suite: Suite): void;
75
+ onBegin(config: FullConfig, suite: Suite): void;
76
+ private resolveRunConfig;
15
77
  onTestEnd(test: TestCase, result: TestResult): void;
16
- onEnd(result: FullResult): Promise<void>;
78
+ onEnd: (result: FullResult) => Promise<void>;
79
+ private drainAndComplete;
80
+ private armFlushTimer;
81
+ private stopFlushTimer;
82
+ private giveUp;
83
+ private triggerFlush;
84
+ private flush;
85
+ private sendBatch;
86
+ private beginRun;
87
+ private completeRun;
88
+ private fetchRules;
89
+ private requestJson;
17
90
  private uploadArtifacts;
18
91
  private presignAndUpload;
19
92
  }
93
+ export declare const __internal: {
94
+ truncate: typeof truncate;
95
+ truncateOrNull: typeof truncateOrNull;
96
+ mapStatus: typeof mapStatus;
97
+ stripAnsi: typeof stripAnsi;
98
+ normalizeRevision: typeof normalizeRevision;
99
+ resolveTestedRevision: typeof resolveTestedRevision;
100
+ toPosixPath: typeof toPosixPath;
101
+ detectCiRunId: typeof detectCiRunId;
102
+ resolveProjectName: typeof resolveProjectName;
103
+ maskSecrets: typeof maskSecrets;
104
+ prepareConsoleOutput: typeof prepareConsoleOutput;
105
+ mapErrors: typeof mapErrors;
106
+ parseRetryAfterMs: typeof parseRetryAfterMs;
107
+ computeBackoffMs: typeof computeBackoffMs;
108
+ attemptQualifiesForArtifacts: typeof attemptQualifiesForArtifacts;
109
+ artifactKey: typeof artifactKey;
110
+ };
20
111
  export {};