squally-reporter 0.13.0 → 0.14.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/README.md +539 -509
- package/dist/ci-identity.d.ts +35 -0
- package/dist/ci-identity.d.ts.map +1 -0
- package/dist/ci-identity.js +206 -0
- package/dist/ci-identity.js.map +1 -0
- package/dist/cli.d.ts +35 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +240 -0
- package/dist/cli.js.map +1 -0
- package/dist/http-config.d.ts +5 -0
- package/dist/http-config.d.ts.map +1 -0
- package/dist/http-config.js +34 -0
- package/dist/http-config.js.map +1 -0
- package/dist/index.d.ts +2 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +119 -324
- package/dist/index.js.map +1 -1
- package/dist/text.d.ts +3 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +23 -0
- package/dist/text.js.map +1 -0
- package/dist/trace-zip.d.ts.map +1 -1
- package/dist/trace-zip.js +2 -1
- package/dist/trace-zip.js.map +1 -1
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -1,509 +1,539 @@
|
|
|
1
|
-
# squally-reporter
|
|
2
|
-
|
|
3
|
-
Playwright reporter that sends test results to your Squally dashboard.
|
|
4
|
-
|
|
5
|
-
Learn more at [app.squally.dev](https://app.squally.dev).
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
- Node.js >=
|
|
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 one env var (e.g. as a CI secret):
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
SQUALLY_INGEST_KEY=your-project-ingest-key
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Find your ingest key in the Squally dashboard under **Settings › Project ›
|
|
27
|
-
Ingest key**.
|
|
28
|
-
|
|
29
|
-
**Since 0.12.0 the URL defaults to `https://app.squally.dev`.** Set
|
|
30
|
-
`SQUALLY_INGEST_URL` only to report to another Squally. Without a key the
|
|
31
|
-
reporter sends nothing and says so once.
|
|
32
|
-
|
|
33
|
-
**Renamed in 0.9.0.** These were `SQUALLY_API_URL` and `SQUALLY_API_KEY`. The
|
|
34
|
-
old names still work for now, with a deprecation warning in the CI log - but
|
|
35
|
-
they belong to Squally's read API now, so rename them. `SQUALLY_API_URL` is
|
|
36
|
-
only used together with `SQUALLY_API_KEY`, never on its own - and when it is
|
|
37
|
-
set but not used, the reporter sends nothing rather than falling back to the
|
|
38
|
-
default URL, since the variable says you meant another target.
|
|
39
|
-
|
|
40
|
-
**A read key is never sent.** A key starting with `sqly_ro_` is a read API key,
|
|
41
|
-
for scripts and agents; reporting needs the ingest key. Since 0.12.0 the
|
|
42
|
-
reporter refuses one in any of `SQUALLY_INGEST_KEY`, `SQUALLY_API_KEY` or the
|
|
43
|
-
`apiKey` option before sending anything, and says which one to fix.
|
|
44
|
-
|
|
45
|
-
Then add the reporter to `playwright.config.ts`:
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
import { defineConfig } from "@playwright/test";
|
|
49
|
-
|
|
50
|
-
export default defineConfig({
|
|
51
|
-
reporter: [
|
|
52
|
-
["list"],
|
|
53
|
-
["squally-reporter"],
|
|
54
|
-
],
|
|
55
|
-
});
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Alternatively, pass config explicitly instead of using env vars - but **prefer
|
|
59
|
-
the env vars above for `apiKey`, and not only because a literal in a committed
|
|
60
|
-
file is obviously wrong.** Playwright's `json` and `blob` reporters serialize
|
|
61
|
-
your resolved config into their output file, options and all: an `apiKey`
|
|
62
|
-
passed here ends up in that file in plain text, even when its value came from
|
|
63
|
-
an environment variable at runtime. What gets written is the option, not where
|
|
64
|
-
it came from. The reporter warns, naming the file, when it sees this
|
|
65
|
-
combination. Measured against a real report, 20.09.2026.
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
reporter: [
|
|
69
|
-
["squally-reporter", { apiUrl: "https://app.squally.dev", apiKey: process.env.SQUALLY_INGEST_KEY }],
|
|
70
|
-
],
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
That's it - run your tests as usual (`npx playwright test`); results stream
|
|
74
|
-
to Squally while the suite runs (see *How results reach Squally* below).
|
|
75
|
-
|
|
76
|
-
## Artifact upload (screenshot, trace, browser console)
|
|
77
|
-
|
|
78
|
-
If your `playwright.config.ts` captures screenshots or traces, the reporter
|
|
79
|
-
uploads them to Squally so they show up next to the failing test in the
|
|
80
|
-
dashboard - no separate setup beyond the two env vars above.
|
|
81
|
-
|
|
82
|
-
Three kinds are uploaded:
|
|
83
|
-
|
|
84
|
-
| Kind | Where it comes from |
|
|
85
|
-
| --- | --- |
|
|
86
|
-
| **Screenshot** | the file Playwright writes for a failed attempt |
|
|
87
|
-
| **Trace** | the trace zip Playwright writes |
|
|
88
|
-
| **Browser console** | read out of that trace zip by this reporter |
|
|
89
|
-
|
|
90
|
-
**The browser console needs a trace.** It is not something Playwright hands a
|
|
91
|
-
reporter: `result.stdout`/`stderr` are the output of the *Node* process - what
|
|
92
|
-
your test file printed - while what the *page* printed only exists inside the
|
|
93
|
-
trace. So the reporter opens the trace and reads it out, which means an
|
|
94
|
-
attempt with no trace has no browser console, and nothing can change that but
|
|
95
|
-
turning tracing on. Squally's attempt page says which of the two it is rather
|
|
96
|
-
than showing an empty section.
|
|
97
|
-
|
|
98
|
-
Supported trace schema versions: **5 to 10**, which covers Playwright 1.30
|
|
99
|
-
through 1.62 (1.62 writes version 8). A trace outside that range is left alone
|
|
100
|
-
and the reporter prints one warning naming the version - it never uploads an
|
|
101
|
-
empty console file, because "no messages" and "this reporter could not read
|
|
102
|
-
it" must not look the same in the dashboard.
|
|
103
|
-
|
|
104
|
-
**Only from attempts worth looking at:** artifacts are uploaded only for
|
|
105
|
-
attempts that **failed**, or that **needed a retry** (even if that retry then
|
|
106
|
-
passed) - never for an attempt that passed on its first try, regardless of how
|
|
107
|
-
`screenshot`/`trace` are configured. Nobody looks at a screenshot of a test
|
|
108
|
-
that just passed, and uploading one anyway would only eat into your
|
|
109
|
-
organization's storage for no benefit.
|
|
110
|
-
|
|
111
|
-
**You need to actually enable capture in `playwright.config.ts`** - without
|
|
112
|
-
it, Playwright doesn't produce any attachments, so there's nothing for the
|
|
113
|
-
reporter to find or upload (no error either way, it's a silent no-op):
|
|
114
|
-
|
|
115
|
-
```ts
|
|
116
|
-
export default defineConfig({
|
|
117
|
-
use: {
|
|
118
|
-
screenshot: "only-on-failure",
|
|
119
|
-
trace: "retain-on-failure",
|
|
120
|
-
},
|
|
121
|
-
// ...
|
|
122
|
-
});
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**Limits.** None of these are enforced by the reporter - Squally applies them
|
|
126
|
-
when the upload is requested and answers with a reason, which is what the
|
|
127
|
-
reporter prints. The numbers below are therefore Squally's current ones rather
|
|
128
|
-
than anything this package guarantees, and can change server-side without a new
|
|
129
|
-
release of it:
|
|
130
|
-
|
|
131
|
-
- **50 MB per screenshot or trace, 4 MB per browser console**, the same on
|
|
132
|
-
every plan. A larger file is rejected before any bytes are uploaded; the
|
|
133
|
-
reporter logs a `console.warn` naming the file and its limit and moves on.
|
|
134
|
-
The test result itself is reported as usual, and the run's outcome and exit
|
|
135
|
-
code are untouched. In the dashboard that artifact is shown as skipped, with
|
|
136
|
-
the reason, rather than silently missing.
|
|
137
|
-
- **At most 250 artifacts per shard.** Squally keeps the first 250 and skips
|
|
138
|
-
the rest with the reason `limit`, which the reporter explains in one line.
|
|
139
|
-
- **A total storage cap per organization, depending on your Squally plan.**
|
|
140
|
-
Once it is reached, further uploads are rejected the same way until you free
|
|
141
|
-
up space or upgrade. Note that a fully instrumented suite now produces up to
|
|
142
|
-
three artifacts per failed attempt rather than two.
|
|
143
|
-
- The browser console is a text file of a few KB - it is extracted from the
|
|
144
|
-
trace you are already uploading, so it adds no capture cost to your run.
|
|
145
|
-
- **Your test's own `stdout`/`stderr` is cut at 16 KB** before it is sent. That
|
|
146
|
-
is this reporter's own limit, not Squally's, and it applies whether or not
|
|
147
|
-
you capture artifacts at all.
|
|
148
|
-
- Two minutes per upload, and 30 seconds for the calls that surround it. An
|
|
149
|
-
unreachable or very slow server means a skipped upload and a `console.warn`,
|
|
150
|
-
not a CI job waiting indefinitely.
|
|
151
|
-
|
|
152
|
-
Upload is best-effort and fully non-blocking, same principle as the rest of
|
|
153
|
-
this reporter - a failed or skipped upload never fails your test run or
|
|
154
|
-
changes its exit code, it only logs a `console.warn` explaining what
|
|
155
|
-
happened.
|
|
156
|
-
|
|
157
|
-
## How results reach Squally
|
|
158
|
-
|
|
159
|
-
Results stream to Squally as your suite runs, not all at once at the end:
|
|
160
|
-
each test's result is sent in a small batch, so the "running" view in the
|
|
161
|
-
dashboard stays close to real time even for a long suite. A batch goes out on
|
|
162
|
-
whichever of these comes first:
|
|
163
|
-
|
|
164
|
-
- **25 results.** Fixed, and the same whether you shard or not.
|
|
165
|
-
- **A timer: 2 seconds, or 0.4 seconds × the number of shards when that is
|
|
166
|
-
longer, capped at 30 seconds** (0.12.0). Up to five shards every shard
|
|
167
|
-
flushes every 2 seconds; eight shards every 3.2; sixteen every 6.4; from 75
|
|
168
|
-
shards the cap holds it at 30.
|
|
169
|
-
|
|
170
|
-
Only the timer scales. All shards of a run share one rate budget on Squally's
|
|
171
|
-
side (300 requests a minute per key), and the timer keeps all of them
|
|
172
|
-
together at about half of it. The 25-result threshold is left alone
|
|
173
|
-
deliberately: it bounds how long a *fast* suite waits, and stretching it would
|
|
174
|
-
delay results without bounding the burst any better.
|
|
175
|
-
|
|
176
|
-
A batch also stays under 3 MB (0.12.0): results with long error texts or many
|
|
177
|
-
steps simply go out in more, smaller requests.
|
|
178
|
-
|
|
179
|
-
There is no longer a single-request size limit that could discard a whole run -
|
|
180
|
-
a very large suite simply takes more, smaller requests. As a courtesy the
|
|
181
|
-
reporter still warns if a run's result count climbs well past 5,000, since that
|
|
182
|
-
usually means retries are configured higher than intended rather than a suite
|
|
183
|
-
that's actually that large.
|
|
184
|
-
|
|
185
|
-
If Squally is unreachable partway through, results already sent stay in the
|
|
186
|
-
dashboard; only what could not be delivered is lost, and your test run's exit
|
|
187
|
-
code is never affected either way.
|
|
188
|
-
|
|
189
|
-
## Steps (0.12.0)
|
|
190
|
-
|
|
191
|
-
For an attempt that **failed, or needed a retry**, the reporter also sends the
|
|
192
|
-
steps Playwright recorded - hooks, fixtures, actions, assertions and your own
|
|
193
|
-
`test.step`s, with their durations and `file:line`. Squally shows them on the
|
|
194
|
-
attempt page, the failing step marked. An attempt that passed on its first try
|
|
195
|
-
sends none.
|
|
196
|
-
|
|
197
|
-
- **Typed values never leave your runner.** The value of a `fill`, `type` or
|
|
198
|
-
`pressSequentially` is replaced: `Fill "•••" getByLabel('Password')`. Every
|
|
199
|
-
title also goes through the same secret masking as error texts.
|
|
200
|
-
- **At most 300 steps per attempt, 200 characters per title.** A longer list
|
|
201
|
-
keeps the failing step and the steps it sits in, then the rest in order, and
|
|
202
|
-
tells Squally how many were left out.
|
|
203
|
-
- Nothing to configure: steps come from Playwright's own report, with or
|
|
204
|
-
without tracing.
|
|
205
|
-
|
|
206
|
-
**Only actual test runs appear.** A run shows up in Squally once your first
|
|
207
|
-
test starts, so commands that never run a test create nothing: `--list`,
|
|
208
|
-
a `globalSetup` or `webServer` that fails to start, a `--grep` that matches
|
|
209
|
-
nothing. A run that *does* execute and legitimately contains no tests — an
|
|
210
|
-
empty shard, `--only-changed` with no changed test file, `--pass-with-no-tests`
|
|
211
|
-
— is still reported, as a run with zero results.
|
|
212
|
-
|
|
213
|
-
**Timeouts and interrupted tests.** A test that runs out of time is reported
|
|
214
|
-
as failed, and Squally is told that it timed out and what its timeout was -
|
|
215
|
-
including a `test.setTimeout()` or `test.slow()` from inside the test. A test
|
|
216
|
-
that Playwright cuts off because the whole run is stopping (`maxFailures`
|
|
217
|
-
reached, Ctrl+C) is reported as skipped: it did not fail, and it does not
|
|
218
|
-
count against the test.
|
|
219
|
-
|
|
220
|
-
## Cancelled runs
|
|
221
|
-
|
|
222
|
-
**Cancelled in Squally.** An admin can cancel a running run from its page in
|
|
223
|
-
Squally. That marks the run there; it does not stop your CI job. What the job
|
|
224
|
-
sends afterwards is discarded, and from 0.12.0 the reporter says so in one
|
|
225
|
-
line - "This run was cancelled in Squally" - instead of advice about job keys.
|
|
226
|
-
|
|
227
|
-
A run stopped with SIGINT - Ctrl+C, or a cancelled CI job - shows in Squally
|
|
228
|
-
as **Cancelled**: the reporter sends what finished and tells Squally the run
|
|
229
|
-
was interrupted. Your exit code stays Playwright's own (130). This needs
|
|
230
|
-
Playwright itself to receive the signal.
|
|
231
|
-
|
|
232
|
-
**On GitHub Actions
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
that
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
- **
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
with `
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
`
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
`
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
1
|
+
# squally-reporter
|
|
2
|
+
|
|
3
|
+
Playwright reporter that sends test results to your Squally dashboard.
|
|
4
|
+
|
|
5
|
+
Learn more at [app.squally.dev](https://app.squally.dev).
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Node.js >= 20.18.1
|
|
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 one env var (e.g. as a CI secret):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
SQUALLY_INGEST_KEY=your-project-ingest-key
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Find your ingest key in the Squally dashboard under **Settings › Project ›
|
|
27
|
+
Ingest key**.
|
|
28
|
+
|
|
29
|
+
**Since 0.12.0 the URL defaults to `https://app.squally.dev`.** Set
|
|
30
|
+
`SQUALLY_INGEST_URL` only to report to another Squally. Without a key the
|
|
31
|
+
reporter sends nothing and says so once.
|
|
32
|
+
|
|
33
|
+
**Renamed in 0.9.0.** These were `SQUALLY_API_URL` and `SQUALLY_API_KEY`. The
|
|
34
|
+
old names still work for now, with a deprecation warning in the CI log - but
|
|
35
|
+
they belong to Squally's read API now, so rename them. `SQUALLY_API_URL` is
|
|
36
|
+
only used together with `SQUALLY_API_KEY`, never on its own - and when it is
|
|
37
|
+
set but not used, the reporter sends nothing rather than falling back to the
|
|
38
|
+
default URL, since the variable says you meant another target.
|
|
39
|
+
|
|
40
|
+
**A read key is never sent.** A key starting with `sqly_ro_` is a read API key,
|
|
41
|
+
for scripts and agents; reporting needs the ingest key. Since 0.12.0 the
|
|
42
|
+
reporter refuses one in any of `SQUALLY_INGEST_KEY`, `SQUALLY_API_KEY` or the
|
|
43
|
+
`apiKey` option before sending anything, and says which one to fix.
|
|
44
|
+
|
|
45
|
+
Then add the reporter to `playwright.config.ts`:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { defineConfig } from "@playwright/test";
|
|
49
|
+
|
|
50
|
+
export default defineConfig({
|
|
51
|
+
reporter: [
|
|
52
|
+
["list"],
|
|
53
|
+
["squally-reporter"],
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Alternatively, pass config explicitly instead of using env vars - but **prefer
|
|
59
|
+
the env vars above for `apiKey`, and not only because a literal in a committed
|
|
60
|
+
file is obviously wrong.** Playwright's `json` and `blob` reporters serialize
|
|
61
|
+
your resolved config into their output file, options and all: an `apiKey`
|
|
62
|
+
passed here ends up in that file in plain text, even when its value came from
|
|
63
|
+
an environment variable at runtime. What gets written is the option, not where
|
|
64
|
+
it came from. The reporter warns, naming the file, when it sees this
|
|
65
|
+
combination. Measured against a real report, 20.09.2026.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
reporter: [
|
|
69
|
+
["squally-reporter", { apiUrl: "https://app.squally.dev", apiKey: process.env.SQUALLY_INGEST_KEY }],
|
|
70
|
+
],
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
That's it - run your tests as usual (`npx playwright test`); results stream
|
|
74
|
+
to Squally while the suite runs (see *How results reach Squally* below).
|
|
75
|
+
|
|
76
|
+
## Artifact upload (screenshot, trace, browser console)
|
|
77
|
+
|
|
78
|
+
If your `playwright.config.ts` captures screenshots or traces, the reporter
|
|
79
|
+
uploads them to Squally so they show up next to the failing test in the
|
|
80
|
+
dashboard - no separate setup beyond the two env vars above.
|
|
81
|
+
|
|
82
|
+
Three kinds are uploaded:
|
|
83
|
+
|
|
84
|
+
| Kind | Where it comes from |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| **Screenshot** | the file Playwright writes for a failed attempt |
|
|
87
|
+
| **Trace** | the trace zip Playwright writes |
|
|
88
|
+
| **Browser console** | read out of that trace zip by this reporter |
|
|
89
|
+
|
|
90
|
+
**The browser console needs a trace.** It is not something Playwright hands a
|
|
91
|
+
reporter: `result.stdout`/`stderr` are the output of the *Node* process - what
|
|
92
|
+
your test file printed - while what the *page* printed only exists inside the
|
|
93
|
+
trace. So the reporter opens the trace and reads it out, which means an
|
|
94
|
+
attempt with no trace has no browser console, and nothing can change that but
|
|
95
|
+
turning tracing on. Squally's attempt page says which of the two it is rather
|
|
96
|
+
than showing an empty section.
|
|
97
|
+
|
|
98
|
+
Supported trace schema versions: **5 to 10**, which covers Playwright 1.30
|
|
99
|
+
through 1.62 (1.62 writes version 8). A trace outside that range is left alone
|
|
100
|
+
and the reporter prints one warning naming the version - it never uploads an
|
|
101
|
+
empty console file, because "no messages" and "this reporter could not read
|
|
102
|
+
it" must not look the same in the dashboard.
|
|
103
|
+
|
|
104
|
+
**Only from attempts worth looking at:** artifacts are uploaded only for
|
|
105
|
+
attempts that **failed**, or that **needed a retry** (even if that retry then
|
|
106
|
+
passed) - never for an attempt that passed on its first try, regardless of how
|
|
107
|
+
`screenshot`/`trace` are configured. Nobody looks at a screenshot of a test
|
|
108
|
+
that just passed, and uploading one anyway would only eat into your
|
|
109
|
+
organization's storage for no benefit.
|
|
110
|
+
|
|
111
|
+
**You need to actually enable capture in `playwright.config.ts`** - without
|
|
112
|
+
it, Playwright doesn't produce any attachments, so there's nothing for the
|
|
113
|
+
reporter to find or upload (no error either way, it's a silent no-op):
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
export default defineConfig({
|
|
117
|
+
use: {
|
|
118
|
+
screenshot: "only-on-failure",
|
|
119
|
+
trace: "retain-on-failure",
|
|
120
|
+
},
|
|
121
|
+
// ...
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Limits.** None of these are enforced by the reporter - Squally applies them
|
|
126
|
+
when the upload is requested and answers with a reason, which is what the
|
|
127
|
+
reporter prints. The numbers below are therefore Squally's current ones rather
|
|
128
|
+
than anything this package guarantees, and can change server-side without a new
|
|
129
|
+
release of it:
|
|
130
|
+
|
|
131
|
+
- **50 MB per screenshot or trace, 4 MB per browser console**, the same on
|
|
132
|
+
every plan. A larger file is rejected before any bytes are uploaded; the
|
|
133
|
+
reporter logs a `console.warn` naming the file and its limit and moves on.
|
|
134
|
+
The test result itself is reported as usual, and the run's outcome and exit
|
|
135
|
+
code are untouched. In the dashboard that artifact is shown as skipped, with
|
|
136
|
+
the reason, rather than silently missing.
|
|
137
|
+
- **At most 250 artifacts per shard.** Squally keeps the first 250 and skips
|
|
138
|
+
the rest with the reason `limit`, which the reporter explains in one line.
|
|
139
|
+
- **A total storage cap per organization, depending on your Squally plan.**
|
|
140
|
+
Once it is reached, further uploads are rejected the same way until you free
|
|
141
|
+
up space or upgrade. Note that a fully instrumented suite now produces up to
|
|
142
|
+
three artifacts per failed attempt rather than two.
|
|
143
|
+
- The browser console is a text file of a few KB - it is extracted from the
|
|
144
|
+
trace you are already uploading, so it adds no capture cost to your run.
|
|
145
|
+
- **Your test's own `stdout`/`stderr` is cut at 16 KB** before it is sent. That
|
|
146
|
+
is this reporter's own limit, not Squally's, and it applies whether or not
|
|
147
|
+
you capture artifacts at all.
|
|
148
|
+
- Two minutes per upload, and 30 seconds for the calls that surround it. An
|
|
149
|
+
unreachable or very slow server means a skipped upload and a `console.warn`,
|
|
150
|
+
not a CI job waiting indefinitely.
|
|
151
|
+
|
|
152
|
+
Upload is best-effort and fully non-blocking, same principle as the rest of
|
|
153
|
+
this reporter - a failed or skipped upload never fails your test run or
|
|
154
|
+
changes its exit code, it only logs a `console.warn` explaining what
|
|
155
|
+
happened.
|
|
156
|
+
|
|
157
|
+
## How results reach Squally
|
|
158
|
+
|
|
159
|
+
Results stream to Squally as your suite runs, not all at once at the end:
|
|
160
|
+
each test's result is sent in a small batch, so the "running" view in the
|
|
161
|
+
dashboard stays close to real time even for a long suite. A batch goes out on
|
|
162
|
+
whichever of these comes first:
|
|
163
|
+
|
|
164
|
+
- **25 results.** Fixed, and the same whether you shard or not.
|
|
165
|
+
- **A timer: 2 seconds, or 0.4 seconds × the number of shards when that is
|
|
166
|
+
longer, capped at 30 seconds** (0.12.0). Up to five shards every shard
|
|
167
|
+
flushes every 2 seconds; eight shards every 3.2; sixteen every 6.4; from 75
|
|
168
|
+
shards the cap holds it at 30.
|
|
169
|
+
|
|
170
|
+
Only the timer scales. All shards of a run share one rate budget on Squally's
|
|
171
|
+
side (300 requests a minute per key), and the timer keeps all of them
|
|
172
|
+
together at about half of it. The 25-result threshold is left alone
|
|
173
|
+
deliberately: it bounds how long a *fast* suite waits, and stretching it would
|
|
174
|
+
delay results without bounding the burst any better.
|
|
175
|
+
|
|
176
|
+
A batch also stays under 3 MB (0.12.0): results with long error texts or many
|
|
177
|
+
steps simply go out in more, smaller requests.
|
|
178
|
+
|
|
179
|
+
There is no longer a single-request size limit that could discard a whole run -
|
|
180
|
+
a very large suite simply takes more, smaller requests. As a courtesy the
|
|
181
|
+
reporter still warns if a run's result count climbs well past 5,000, since that
|
|
182
|
+
usually means retries are configured higher than intended rather than a suite
|
|
183
|
+
that's actually that large.
|
|
184
|
+
|
|
185
|
+
If Squally is unreachable partway through, results already sent stay in the
|
|
186
|
+
dashboard; only what could not be delivered is lost, and your test run's exit
|
|
187
|
+
code is never affected either way.
|
|
188
|
+
|
|
189
|
+
## Steps (0.12.0)
|
|
190
|
+
|
|
191
|
+
For an attempt that **failed, or needed a retry**, the reporter also sends the
|
|
192
|
+
steps Playwright recorded - hooks, fixtures, actions, assertions and your own
|
|
193
|
+
`test.step`s, with their durations and `file:line`. Squally shows them on the
|
|
194
|
+
attempt page, the failing step marked. An attempt that passed on its first try
|
|
195
|
+
sends none.
|
|
196
|
+
|
|
197
|
+
- **Typed values never leave your runner.** The value of a `fill`, `type` or
|
|
198
|
+
`pressSequentially` is replaced: `Fill "•••" getByLabel('Password')`. Every
|
|
199
|
+
title also goes through the same secret masking as error texts.
|
|
200
|
+
- **At most 300 steps per attempt, 200 characters per title.** A longer list
|
|
201
|
+
keeps the failing step and the steps it sits in, then the rest in order, and
|
|
202
|
+
tells Squally how many were left out.
|
|
203
|
+
- Nothing to configure: steps come from Playwright's own report, with or
|
|
204
|
+
without tracing.
|
|
205
|
+
|
|
206
|
+
**Only actual test runs appear.** A run shows up in Squally once your first
|
|
207
|
+
test starts, so commands that never run a test create nothing: `--list`,
|
|
208
|
+
a `globalSetup` or `webServer` that fails to start, a `--grep` that matches
|
|
209
|
+
nothing. A run that *does* execute and legitimately contains no tests — an
|
|
210
|
+
empty shard, `--only-changed` with no changed test file, `--pass-with-no-tests`
|
|
211
|
+
— is still reported, as a run with zero results.
|
|
212
|
+
|
|
213
|
+
**Timeouts and interrupted tests.** A test that runs out of time is reported
|
|
214
|
+
as failed, and Squally is told that it timed out and what its timeout was -
|
|
215
|
+
including a `test.setTimeout()` or `test.slow()` from inside the test. A test
|
|
216
|
+
that Playwright cuts off because the whole run is stopping (`maxFailures`
|
|
217
|
+
reached, Ctrl+C) is reported as skipped: it did not fail, and it does not
|
|
218
|
+
count against the test.
|
|
219
|
+
|
|
220
|
+
## Cancelled runs
|
|
221
|
+
|
|
222
|
+
**Cancelled in Squally.** An admin can cancel a running run from its page in
|
|
223
|
+
Squally. That marks the run there; it does not stop your CI job. What the job
|
|
224
|
+
sends afterwards is discarded, and from 0.12.0 the reporter says so in one
|
|
225
|
+
line - "This run was cancelled in Squally" - instead of advice about job keys.
|
|
226
|
+
|
|
227
|
+
A run stopped with SIGINT - Ctrl+C, or a cancelled CI job - shows in Squally
|
|
228
|
+
as **Cancelled**: the reporter sends what finished and tells Squally the run
|
|
229
|
+
was interrupted. Your exit code stays Playwright's own (130). This needs
|
|
230
|
+
Playwright itself to receive the signal.
|
|
231
|
+
|
|
232
|
+
**On GitHub Actions** a cancelled workflow sends the signal to the step's
|
|
233
|
+
shell, not to Playwright, so the reporter never reports the end. Add one step
|
|
234
|
+
after the tests (0.14.0) that marks the run cancelled in Squally:
|
|
235
|
+
|
|
236
|
+
```yaml
|
|
237
|
+
jobs:
|
|
238
|
+
test:
|
|
239
|
+
runs-on: ubuntu-latest
|
|
240
|
+
env:
|
|
241
|
+
SQUALLY_INGEST_KEY: ${{ secrets.SQUALLY_INGEST_KEY }}
|
|
242
|
+
steps:
|
|
243
|
+
# ...checkout, setup-node, npm ci, playwright install
|
|
244
|
+
- name: Run Playwright tests
|
|
245
|
+
run: npx playwright test
|
|
246
|
+
- name: Mark Squally run cancelled
|
|
247
|
+
if: ${{ cancelled() }}
|
|
248
|
+
run: npx squally-reporter cancel
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`npx squally-reporter cancel` finds the run by the same CI run id the reporter
|
|
252
|
+
sent from this job, marks every shard of it that is still running as
|
|
253
|
+
cancelled, and prints one line: "Marked run #N as cancelled in Squally
|
|
254
|
+
(X shards)", "Nothing to cancel: no run recorded for <id>" when the job was
|
|
255
|
+
cancelled before its first test, or "Run already finished". It never fails the
|
|
256
|
+
step: a missing key, an unreachable Squally, a 401 or a 5xx is one warning and
|
|
257
|
+
exit code 0. It needs the key in the step's environment - set it at job
|
|
258
|
+
level, as above, or on the step too. A Squally without the cancel endpoint
|
|
259
|
+
answers 404, which is a warning as well.
|
|
260
|
+
|
|
261
|
+
Two things to know:
|
|
262
|
+
|
|
263
|
+
- **Set `SQUALLY_CI_JOB_KEY` at job level**, not only on the test step. The
|
|
264
|
+
cancel step builds the id the same way the reporter did; without the key it
|
|
265
|
+
names another run - or none. `npx squally-reporter cancel --job-key <key>`
|
|
266
|
+
passes it explicitly.
|
|
267
|
+
- **A fail-fast matrix marks the whole group cancelled.** With GitHub's
|
|
268
|
+
default `fail-fast: true`, one failing shard cancels the others, their
|
|
269
|
+
cancel steps run, and the whole run shows as **Cancelled** instead of
|
|
270
|
+
**Failed**. Set `fail-fast: false` on a sharded job so a
|
|
271
|
+
real failure stays a failure.
|
|
272
|
+
|
|
273
|
+
Without the step, a cancelled workflow shows as **incomplete** in Squally.
|
|
274
|
+
|
|
275
|
+
Without that - or after any hard kill (SIGKILL) - the reporter never gets to
|
|
276
|
+
report the end: the run stays *running* until Squally's hourly check marks it
|
|
277
|
+
**incomplete** once the project's run timeout (60 minutes by default) has
|
|
278
|
+
passed. A run stopped before its first test starts sends nothing, like any
|
|
279
|
+
run that never ran a test, and `globalTimeout` running out is not a cancel -
|
|
280
|
+
that run is simply failed.
|
|
281
|
+
|
|
282
|
+
## Which CI, branch and commit a run is reported under
|
|
283
|
+
|
|
284
|
+
Squally counts **CI runs** in its test metrics, so the reporter works out
|
|
285
|
+
which CI system it is running on from that system's own environment:
|
|
286
|
+
|
|
287
|
+
| Provider | Recognised by | Shards grouped by | Branch | Commit |
|
|
288
|
+
|---|---|---|---|---|
|
|
289
|
+
| GitHub Actions | `GITHUB_ACTIONS` | `GITHUB_RUN_ID`-`GITHUB_RUN_ATTEMPT` | `GITHUB_REF_NAME`; pull requests: `GITHUB_HEAD_REF` | `GITHUB_SHA`; pull requests: the PR's head commit |
|
|
290
|
+
| GitLab CI | `GITLAB_CI` | `CI_PIPELINE_ID` | `CI_COMMIT_BRANCH`; merge requests: `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` | `CI_COMMIT_SHA`; merged-results pipelines: `CI_MERGE_REQUEST_SOURCE_BRANCH_SHA` |
|
|
291
|
+
| CircleCI | `CIRCLECI` | `CIRCLE_WORKFLOW_ID` | `CIRCLE_BRANCH` | `CIRCLE_SHA1` |
|
|
292
|
+
| Jenkins | `BUILD_TAG` (`jenkins-…`) | `BUILD_TAG` | `CHANGE_BRANCH`, else `BRANCH_NAME`, else `GIT_BRANCH` | `GIT_COMMIT` |
|
|
293
|
+
| Azure Pipelines | `TF_BUILD` | `BUILD_BUILDID` | `BUILD_SOURCEBRANCH`; pull requests: `SYSTEM_PULLREQUEST_SOURCEBRANCH` | `BUILD_SOURCEVERSION`; pull requests: `SYSTEM_PULLREQUEST_SOURCECOMMITID` |
|
|
294
|
+
| Bitbucket Pipelines | `BITBUCKET_BUILD_NUMBER` | `BITBUCKET_BUILD_NUMBER` | `BITBUCKET_BRANCH` | `BITBUCKET_COMMIT` |
|
|
295
|
+
| Buildkite | `BUILDKITE` | `BUILDKITE_BUILD_ID` | `BUILDKITE_BRANCH` | `BUILDKITE_COMMIT` |
|
|
296
|
+
| TeamCity | `TEAMCITY_VERSION` | - (set `SQUALLY_CI_RUN_ID`) | from git | `BUILD_VCS_NUMBER` |
|
|
297
|
+
| AWS CodeBuild | `CODEBUILD_BUILD_ID` | `CODEBUILD_BUILD_ID` | `CODEBUILD_WEBHOOK_HEAD_REF` | `CODEBUILD_RESOLVED_SOURCE_VERSION` |
|
|
298
|
+
| Travis CI | `TRAVIS` | `TRAVIS_BUILD_ID` | `TRAVIS_BRANCH`; pull requests: `TRAVIS_PULL_REQUEST_BRANCH` | `TRAVIS_COMMIT`; pull requests: `TRAVIS_PULL_REQUEST_SHA` |
|
|
299
|
+
| Drone | `DRONE` | `DRONE_BUILD_NUMBER` | `DRONE_BRANCH`; pull requests: `DRONE_SOURCE_BRANCH` | `DRONE_COMMIT_SHA` |
|
|
300
|
+
| any other CI | `CI=true` or `CI=1` | - (set `SQUALLY_CI_RUN_ID`) | from git | from git |
|
|
301
|
+
|
|
302
|
+
- **`CI` counts only as `true` or `1`.** `CI=false` or `CI=0` is a local run -
|
|
303
|
+
unless one of the providers above is recognised, which always wins. A run
|
|
304
|
+
that is none of these is local. Local runs are listed with all other runs,
|
|
305
|
+
and have numbers of their own: Squally's figures count CI runs by default,
|
|
306
|
+
and the **All · CI · Local** switch shows local runs on their own, or both.
|
|
307
|
+
A local run never posts a GitHub check, a pull-request comment or a Slack
|
|
308
|
+
message.
|
|
309
|
+
- **Pull requests are reported under their own head commit and source
|
|
310
|
+
branch**, not the merge commit CI checks out - that is the commit Squally's
|
|
311
|
+
GitHub check and pull-request comment have to find. The commit message and
|
|
312
|
+
author are that commit's too - never the "Merge … into …" message of the
|
|
313
|
+
checkout.
|
|
314
|
+
- **The head commit is fetched when the checkout lacks it.** With
|
|
315
|
+
`actions/checkout`'s default `fetch-depth: 1` the head commit is not in the
|
|
316
|
+
clone, so the reporter fetches that one commit from `origin`
|
|
317
|
+
(`git fetch --depth=1 --no-tags`), waits at most 3 seconds, and reads its
|
|
318
|
+
message and author. It only does so in a shallow checkout - fetching into a
|
|
319
|
+
full clone would make it shallow for the rest of your job - and never
|
|
320
|
+
changes `HEAD` or any branch. If the fetch fails or takes longer, the
|
|
321
|
+
message and author stay empty and the reporter says so once; the run and
|
|
322
|
+
its exit code are unaffected. Alternatives: `fetch-depth: 2` on the checkout
|
|
323
|
+
step puts the head commit in the clone, so nothing is fetched, and
|
|
324
|
+
`SQUALLY_DISABLE_HEAD_COMMIT_FETCH=true` turns the fetch off.
|
|
325
|
+
- **A tag build has no branch.** A detached checkout never reports `HEAD` as a
|
|
326
|
+
branch name, and `refs/heads/` or `origin/` prefixes are removed.
|
|
327
|
+
- **A link to the CI run (0.11.0).** The reporter sends the page of the CI run,
|
|
328
|
+
so Squally can link a run to it. It is read from each provider's own
|
|
329
|
+
variable, sent only as an `https://` URL of at most 2,048 characters, and
|
|
330
|
+
never opened by Squally - only shown as a link:
|
|
331
|
+
|
|
332
|
+
| Provider | Link |
|
|
333
|
+
|---|---|
|
|
334
|
+
| GitHub Actions | `GITHUB_SERVER_URL`/`GITHUB_REPOSITORY`/actions/runs/`GITHUB_RUN_ID`/attempts/`GITHUB_RUN_ATTEMPT` |
|
|
335
|
+
| GitLab CI | `CI_PIPELINE_URL` |
|
|
336
|
+
| Jenkins | `BUILD_URL` (only with Jenkins served over https) |
|
|
337
|
+
| Buildkite | `BUILDKITE_BUILD_URL` |
|
|
338
|
+
| Travis CI | `TRAVIS_BUILD_WEB_URL` |
|
|
339
|
+
| Drone | `DRONE_BUILD_LINK` |
|
|
340
|
+
|
|
341
|
+
Other providers send no link. It is the run's page, never one job's, and it
|
|
342
|
+
is the same with `SQUALLY_CI_RUN_ID` set.
|
|
343
|
+
|
|
344
|
+
## Tests marked with `test.fail()`
|
|
345
|
+
|
|
346
|
+
Playwright's `test.fail()` marks a test as *expected to fail*: it guards a bug
|
|
347
|
+
you already know about, and Playwright runs the test anyway to check that it
|
|
348
|
+
does still fail. The interesting moment is the opposite one - when someone
|
|
349
|
+
fixes the bug, the test starts passing and Playwright turns it red so you
|
|
350
|
+
remember to delete the marker.
|
|
351
|
+
|
|
352
|
+
The reporter passes that marker on, so Squally reads such a test the same way
|
|
353
|
+
Playwright does:
|
|
354
|
+
|
|
355
|
+
- **Marked and still failing** - shown as passing, with an "expected failure"
|
|
356
|
+
label. It counts as a pass in the test's stability, not against its failure
|
|
357
|
+
rate, and it does not fail Squally's pull-request check.
|
|
358
|
+
- **Marked and now passing** - shown as a failure, because the marker is out of
|
|
359
|
+
date and should be removed.
|
|
360
|
+
- **Marked and timed out** - shown as a failure. Playwright does not count a
|
|
361
|
+
timeout as the expected failure either, so the marker is not passed on for
|
|
362
|
+
it.
|
|
363
|
+
|
|
364
|
+
Both ways of marking a test are recognised: the `test.fail()` modifier and a
|
|
365
|
+
`testInfo.fail()` call inside the test body. `test.skip()` and `test.fixme()`
|
|
366
|
+
are a different statement and are not affected.
|
|
367
|
+
|
|
368
|
+
Nothing is reported for an unmarked test, so an ordinary suite is unchanged.
|
|
369
|
+
Note also that Playwright itself treats a marked, failing test as expected, so
|
|
370
|
+
with `screenshot: "only-on-failure"` it captures nothing for it - there is no
|
|
371
|
+
artifact for the reporter to upload, which is consistent rather than missing.
|
|
372
|
+
|
|
373
|
+
## Sharding
|
|
374
|
+
|
|
375
|
+
**Handled automatically - no extra setup needed.** Each shard is still a
|
|
376
|
+
separate process with its own reporter instance, but Squally now correlates
|
|
377
|
+
them itself: all shards of one CI run are grouped together and shown as one
|
|
378
|
+
run once every shard has finished, with the flip side of that guarantee being
|
|
379
|
+
that the run only completes once every shard *has* reported.
|
|
380
|
+
|
|
381
|
+
Correlation is based on an id read from your CI provider's own environment
|
|
382
|
+
(the "Shards grouped by" column above), so it costs you nothing on every
|
|
383
|
+
provider in that table except TeamCity. On TeamCity, on a CI system not in the
|
|
384
|
+
table, or when you shard locally across several separate `--shard`
|
|
385
|
+
invocations, set `SQUALLY_CI_RUN_ID` to the same value for every shard of one
|
|
386
|
+
run - anything unique per CI run and shared by its shards works, e.g. a build
|
|
387
|
+
number your CI system already exposes.
|
|
388
|
+
|
|
389
|
+
**Several jobs in one CI run need their own key (0.11.0).** The id is shared
|
|
390
|
+
by everything in one CI run, so two jobs that are *not* shards of each other -
|
|
391
|
+
a GitHub Actions matrix over browsers run without `--shard`, or an `api` and a
|
|
392
|
+
`ui` suite - would report into the same Squally run. Give each job its own
|
|
393
|
+
`SQUALLY_CI_JOB_KEY`, shared only by that job's own shards:
|
|
394
|
+
|
|
395
|
+
```yaml
|
|
396
|
+
jobs:
|
|
397
|
+
test:
|
|
398
|
+
strategy:
|
|
399
|
+
matrix:
|
|
400
|
+
browser: [chromium, firefox]
|
|
401
|
+
env:
|
|
402
|
+
SQUALLY_CI_JOB_KEY: ${{ matrix.browser }}
|
|
403
|
+
steps:
|
|
404
|
+
- run: npx playwright test --project=${{ matrix.browser }}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
The reporter puts the key into the id - `<GITHUB_RUN_ID>-<key>-<GITHUB_RUN_ATTEMPT>`
|
|
408
|
+
on GitHub Actions, `<provider id>-<key>` elsewhere - so each job is its own run
|
|
409
|
+
in Squally, and a re-run of one job links to that job's own earlier attempt.
|
|
410
|
+
The key may hold letters, digits, `.`, `_` and `-`, at most 64 characters; an
|
|
411
|
+
invalid key is reported once and ignored, and your test run is unaffected.
|
|
412
|
+
Without a key the id is exactly what it was before 0.11.0. A suite that is
|
|
413
|
+
sharded *and* split into jobs uses the same key for all shards of one job:
|
|
414
|
+
`SQUALLY_CI_JOB_KEY: ${{ matrix.suite }}` beside `--shard=${{ matrix.shard }}/2`.
|
|
415
|
+
|
|
416
|
+
**Replaces the hand-built id.** Until 0.11.0 this section suggested
|
|
417
|
+
`SQUALLY_CI_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.browser }}`.
|
|
418
|
+
It still groups each job correctly, but Squally cannot tell its attempts apart,
|
|
419
|
+
so a re-run is never linked to the attempt before it. Switch to
|
|
420
|
+
`SQUALLY_CI_JOB_KEY`. An explicit `SQUALLY_CI_RUN_ID` still wins for the id;
|
|
421
|
+
the key is then only sent alongside it.
|
|
422
|
+
|
|
423
|
+
**When two jobs collide anyway, Squally refuses the second one at the start**
|
|
424
|
+
(squally-app with block 1b, reporter 0.11.0). The reporter prints one block,
|
|
425
|
+
sends nothing more for that job, and leaves your test run and its exit code
|
|
426
|
+
alone:
|
|
427
|
+
|
|
428
|
+
- `ci_run_collision` - another job already reports under this CI run id (and
|
|
429
|
+
shard). Give every job its own `SQUALLY_CI_JOB_KEY`.
|
|
430
|
+
- `ci_run_mixed` - the id would put a sharded job and an unsharded one, or
|
|
431
|
+
shards of different totals, into one run. The same fix.
|
|
432
|
+
|
|
433
|
+
Before 0.11.0 the second job was handed the first job's run instead, and its
|
|
434
|
+
results were refused once that run completed; a reporter older than 0.11.0
|
|
435
|
+
still behaves that way, and prints "Squally has already completed run …".
|
|
436
|
+
|
|
437
|
+
**Re-runs.** On GitHub Actions every re-run attempt is its own run in Squally,
|
|
438
|
+
linked to the attempt before it ("Re-run of #N"). "Re-run failed jobs" runs only
|
|
439
|
+
the shards that did not pass; that attempt waits only for those shards,
|
|
440
|
+
completes once they have reported, and passes or fails on the tests they ran -
|
|
441
|
+
the reused shards are neither waited for nor counted, and the run page shows
|
|
442
|
+
them as "passed in #N · not re-run". A re-run shard that never reports still
|
|
443
|
+
leaves the run incomplete after the run timeout.
|
|
444
|
+
|
|
445
|
+
Other CI systems re-run a job under the id of the run it belongs to - a GitLab
|
|
446
|
+
job retry, an Azure Pipelines re-run, a Bitbucket re-run of failed steps, a
|
|
447
|
+
Buildkite retry, a Travis CI restart. Since 0.11.0 Squally refuses such a
|
|
448
|
+
retry at the start with `ci_run_collision`: the slot is taken by the first
|
|
449
|
+
attempt. **`SQUALLY_CI_JOB_KEY` does not help here** - the retry is the same
|
|
450
|
+
job - so a retry on these systems is not reported; the first attempt stays the
|
|
451
|
+
run. A Drone restart is a new build with a new number, and so a new run in
|
|
452
|
+
Squally, not linked to the one it repeats.
|
|
453
|
+
|
|
454
|
+
**`playwright merge-reports` is not part of this and is not recommended** for
|
|
455
|
+
getting a single run in Squally - the automatic correlation above is the
|
|
456
|
+
supported path, and produces correct per-shard timing/branch/commit
|
|
457
|
+
information that a merge job cannot (see the historical note below). Nothing
|
|
458
|
+
stops you from using `merge-reports` for Playwright's own HTML report if you
|
|
459
|
+
want that separately; just don't point this reporter at the merged result
|
|
460
|
+
expecting Squally to treat it specially.
|
|
461
|
+
|
|
462
|
+
<details>
|
|
463
|
+
<summary>Why not just use `merge-reports`, historically</summary>
|
|
464
|
+
|
|
465
|
+
An earlier version of this reporter recommended `playwright merge-reports` as
|
|
466
|
+
the way to get one Squally run out of a sharded job. That path has a real
|
|
467
|
+
problem: branch, commit, commit message and author are all read on whichever
|
|
468
|
+
machine the reporter actually runs on - during a merge that is the merge job,
|
|
469
|
+
not the shards - and without a checkout there (and without the CI provider's
|
|
470
|
+
own commit variables) they all come out empty. A run with no branch is in
|
|
471
|
+
no branch's numbers and no branch filter finds it, so a "merged" run could
|
|
472
|
+
exist in the dashboard while missing from everything Squally measures per
|
|
473
|
+
branch: suite
|
|
474
|
+
stability, and each test's stability, flaky rate and failure rate on that
|
|
475
|
+
branch. The automatic correlation above reads that information from each
|
|
476
|
+
shard's own environment instead, so this problem does not arise.
|
|
477
|
+
|
|
478
|
+
</details>
|
|
479
|
+
|
|
480
|
+
## Tested revision (optional)
|
|
481
|
+
|
|
482
|
+
The tested revision is the state of your **application** a run tested, as
|
|
483
|
+
opposed to the commit the run is reported under. Squally does not know it by
|
|
484
|
+
itself - it is **opt-in**, with one of these two:
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
# The tests live in the same repo as the application: the commit this CI job
|
|
488
|
+
# checked out is the tested revision.
|
|
489
|
+
SAME_REPO_AS_APP=true
|
|
490
|
+
|
|
491
|
+
# The tests live in a separate repo (e.g. a dedicated E2E repo pointed at a
|
|
492
|
+
# deployed app): pass the tested revision itself - a commit SHA, tag, or
|
|
493
|
+
# branch name/ref.
|
|
494
|
+
TESTED_REVISION=v1.2.0
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
- **`SAME_REPO_AS_APP=true`** sends the commit your job checked out: your CI
|
|
498
|
+
provider's own variable for it (`GITHUB_SHA`, `CI_COMMIT_SHA`,
|
|
499
|
+
`CIRCLE_SHA1`, `GIT_COMMIT`, `BUILD_SOURCEVERSION`, `BITBUCKET_COMMIT`,
|
|
500
|
+
`BUILDKITE_COMMIT`, `BUILD_VCS_NUMBER`, `CODEBUILD_RESOLVED_SOURCE_VERSION`,
|
|
501
|
+
`TRAVIS_COMMIT`, `DRONE_COMMIT_SHA`), or `git rev-parse HEAD` where there is
|
|
502
|
+
none - including local runs. Only a commit SHA is sent; if neither yields
|
|
503
|
+
one, the run has no tested revision.
|
|
504
|
+
- **On a pull request the two commits differ, on purpose.** The run is
|
|
505
|
+
reported under the pull request's head commit (see the provider table
|
|
506
|
+
above) - that is what Squally's check and PR comment need. The tested
|
|
507
|
+
revision is the merge commit CI checked out, because that is what the tests
|
|
508
|
+
ran against. On a Bitbucket pull request and a GitHub `pull_request_target`
|
|
509
|
+
run it comes from `git rev-parse HEAD`.
|
|
510
|
+
- **`TESTED_REVISION` wins** when both are set.
|
|
511
|
+
- Neither variable is required - without them, test results are still sent
|
|
512
|
+
and stored exactly as before.
|
|
513
|
+
|
|
514
|
+
**The value is normalised before it is stored**, so that the same revision
|
|
515
|
+
written two ways still compares equal: it is lowercased, and a leading `v` or a
|
|
516
|
+
`refs/heads/` prefix is removed. The example above is exactly such a case -
|
|
517
|
+
`v1.2.0` is stored as `1.2.0`. The reporter says so once per run when it
|
|
518
|
+
happens, so a value that looks different in the dashboard is not a mystery.
|
|
519
|
+
|
|
520
|
+
## Troubleshooting
|
|
521
|
+
|
|
522
|
+
Set `SQUALLY_DEBUG=1` to print detail that is otherwise kept quiet - the
|
|
523
|
+
browser-console extraction (how many messages were found, or that an attempt's
|
|
524
|
+
trace held none) and, since 0.11.0, a CI run link that was not sent because it
|
|
525
|
+
was not an `https://` URL. Everything that is *not* ordinary (a trace that
|
|
526
|
+
cannot be read, an unsupported schema version, a rejected upload) is printed
|
|
527
|
+
without it.
|
|
528
|
+
|
|
529
|
+
## Support
|
|
530
|
+
|
|
531
|
+
Found a bug or have a question? Please open an issue on
|
|
532
|
+
[GitHub Issues](https://github.com/squallydev-admin/squally-reporter/issues) -
|
|
533
|
+
that's the primary way to report problems or ask questions. For anything you
|
|
534
|
+
would rather not post publicly, write to
|
|
535
|
+
[support@squally.dev](mailto:support@squally.dev).
|
|
536
|
+
|
|
537
|
+
## License
|
|
538
|
+
|
|
539
|
+
MIT - see [LICENSE](./LICENSE).
|