browser-commander 0.18.0 → 0.19.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.
@@ -1,5 +1,6 @@
1
1
  import { describe, it } from 'node:test';
2
2
  import assert from 'node:assert';
3
+ import { setImmediate as yieldToEngine } from 'node:timers/promises';
3
4
  import fs from 'node:fs/promises';
4
5
  import path from 'node:path';
5
6
 
@@ -7,6 +8,7 @@ import { startTrace } from '../../../src/traces/recorder.js';
7
8
  import { readTrace } from '../../../src/traces/reader.js';
8
9
  import { REDACTED } from '../../../src/traces/redaction.js';
9
10
  import {
11
+ TRACE_CHECKPOINT_REASON,
10
12
  TRACE_DROP_REASON,
11
13
  TRACE_EVENT,
12
14
  TRACE_MODE,
@@ -232,7 +234,9 @@ describe('trace recorder (issue #87)', () => {
232
234
  const installs = page.state.evaluated.filter(
233
235
  (call) => call.name === 'installMutationRecorderInPage'
234
236
  );
235
- assert.strictEqual(installs.length, 2);
237
+ // Once at the start, and once after each of the two checkpoints - the
238
+ // base one the recorder takes itself and the one named above.
239
+ assert.strictEqual(installs.length, 3);
236
240
  });
237
241
  });
238
242
 
@@ -272,6 +276,170 @@ describe('trace recorder (issue #87)', () => {
272
276
  });
273
277
  });
274
278
 
279
+ describe('continuity (issue #93)', () => {
280
+ it('should take a base snapshot a continuous trace can replay from', async () => {
281
+ const { trace } = await start({ mode: TRACE_MODE.CONTINUOUS });
282
+ const stopped = await trace.stop();
283
+
284
+ const [base] = stopped.checkpoints;
285
+ assert.strictEqual(base.name, 'initial');
286
+ assert.strictEqual(base.actor, 'recorder');
287
+ assert.strictEqual(base.reason, TRACE_CHECKPOINT_REASON.INITIAL);
288
+ });
289
+
290
+ it('should leave a checkpoints-only trace the moments its caller named', async () => {
291
+ const { trace } = await start({ mode: TRACE_MODE.CHECKPOINTS });
292
+ await trace.checkpoint('only this one');
293
+ const stopped = await trace.stop();
294
+
295
+ assert.deepStrictEqual(
296
+ stopped.checkpoints.map((entry) => entry.name),
297
+ ['only this one']
298
+ );
299
+ });
300
+
301
+ it('should let the caller name the base snapshot', async () => {
302
+ const { trace } = await start({
303
+ mode: TRACE_MODE.CONTINUOUS,
304
+ initialCheckpoint: 'before anything',
305
+ });
306
+ const stopped = await trace.stop();
307
+
308
+ assert.strictEqual(stopped.checkpoints[0].name, 'before anything');
309
+ });
310
+
311
+ it('should let the caller refuse the base snapshot', async () => {
312
+ const { trace } = await start({
313
+ mode: TRACE_MODE.CONTINUOUS,
314
+ initialCheckpoint: false,
315
+ });
316
+ const stopped = await trace.stop();
317
+
318
+ assert.deepStrictEqual(stopped.checkpoints, []);
319
+ const started = await eventOfKind(stopped, TRACE_EVENT.TRACE_START);
320
+ assert.strictEqual(started.initialCheckpoint, false);
321
+ });
322
+
323
+ it('should observe a document created after the trace started', async () => {
324
+ const { trace, page } = await start({ mode: TRACE_MODE.CONTINUOUS });
325
+ await trace.stop();
326
+
327
+ // Registered with the engine rather than evaluated: this is what runs
328
+ // before a freshly navigated document's own scripts do.
329
+ assert.deepStrictEqual(
330
+ page.state.initScripts.map((script) => script.name),
331
+ ['installMutationRecorderInPage']
332
+ );
333
+ });
334
+
335
+ it('should ask the page to record live control state', async () => {
336
+ const { trace, page } = await start({ mode: TRACE_MODE.CONTINUOUS });
337
+ await trace.stop();
338
+
339
+ const [installed] = page.state.evaluated.filter(
340
+ (call) => call.name === 'installMutationRecorderInPage'
341
+ );
342
+ assert.strictEqual(installed.argument.liveState, true);
343
+ });
344
+
345
+ it('should stop the in-page recorder when the trace stops', async () => {
346
+ const { trace, page } = await start({ mode: TRACE_MODE.CONTINUOUS });
347
+ await trace.stop();
348
+
349
+ const stops = page.state.evaluated.filter(
350
+ (call) => call.name === 'stopMutationRecorderInPage'
351
+ );
352
+ assert.strictEqual(stops.length, 1);
353
+ });
354
+
355
+ it('should name the owner of every timeline record', async () => {
356
+ const { trace } = await start({ mode: TRACE_MODE.CONTINUOUS });
357
+ const stopped = await trace.stop();
358
+
359
+ const events = (await read(stopped)).events;
360
+ assert.ok(events.length > 0);
361
+ for (const event of events) {
362
+ assert.match(event.traceId, /^trace-\d+$/);
363
+ assert.match(event.pageId, /^page-\d+$/);
364
+ assert.match(event.navigationId, /^nav-\d+$/);
365
+ }
366
+ });
367
+
368
+ it('should name the action a recorded interaction belongs to', async () => {
369
+ const { trace, commander } = await start();
370
+ await commander.click('#submit');
371
+ const stopped = await trace.stop();
372
+
373
+ const interaction = await eventOfKind(stopped, TRACE_EVENT.INTERACTION);
374
+ assert.match(interaction.actionId, /^trace-\d+-action-1$/);
375
+ });
376
+
377
+ it('should separate what happened before a navigation from what happened after', async () => {
378
+ const { trace, page } = await start({ mode: TRACE_MODE.CONTINUOUS });
379
+ const before = await trace.checkpoint('before');
380
+
381
+ page.emit('framenavigated', {
382
+ url: () => 'https://example.com/next',
383
+ parentFrame: () => null,
384
+ });
385
+ await yieldToEngine();
386
+
387
+ const after = await trace.checkpoint('after');
388
+ const stopped = await trace.stop();
389
+
390
+ const events = (await read(stopped)).events;
391
+ const owner = (index) =>
392
+ events.find(
393
+ (event) =>
394
+ event.kind === TRACE_EVENT.CHECKPOINT && event.index === index
395
+ ).navigationId;
396
+ assert.notStrictEqual(owner(before.index), owner(after.index));
397
+ });
398
+
399
+ it('should drain every frame of a page, not only the main one', async () => {
400
+ const page = createFakePage({
401
+ mutations: [{ sequence: 1, at: 1, records: [] }],
402
+ frames: [{ mutations: [{ sequence: 1, at: 2, records: [] }] }],
403
+ });
404
+ const { trace } = await start({ page, mode: TRACE_MODE.CONTINUOUS });
405
+ const stopped = await trace.stop();
406
+
407
+ const batches = await (await read(stopped)).mutations(0);
408
+ // Each batch says which document it came from, so two frames that both
409
+ // numbered their batch 1 are still telling two different stories.
410
+ assert.deepStrictEqual(
411
+ batches.map((batch) => batch.frameId),
412
+ ['main', 'child-1']
413
+ );
414
+ assert.deepStrictEqual(
415
+ batches.map((batch) => batch.mainFrame),
416
+ [true, false]
417
+ );
418
+ });
419
+
420
+ it('should say in the manifest what the bundle can actually replay', async () => {
421
+ const { trace } = await start({ mode: TRACE_MODE.CONTINUOUS });
422
+ const stopped = await trace.stop();
423
+
424
+ assert.deepStrictEqual(stopped.manifest.replay, {
425
+ checkpoints: true,
426
+ mutations: true,
427
+ childListPositions: true,
428
+ liveState: true,
429
+ identifiers: true,
430
+ });
431
+ });
432
+
433
+ it('should admit a checkpoints-only bundle cannot replay the gaps', async () => {
434
+ const { trace } = await start({ mode: TRACE_MODE.CHECKPOINTS });
435
+ const stopped = await trace.stop();
436
+
437
+ assert.strictEqual(stopped.manifest.replay.mutations, false);
438
+ assert.strictEqual(stopped.manifest.replay.liveState, false);
439
+ assert.strictEqual(stopped.manifest.replay.checkpoints, true);
440
+ });
441
+ });
442
+
275
443
  describe('the shared timeline', () => {
276
444
  /** A completed download, as the manager reports one. */
277
445
  const COMPLETED_DOWNLOAD = Object.freeze({
@@ -41,6 +41,11 @@ describe('offline trace viewer (issue #87)', () => {
41
41
  output: path.join(directory.path, name),
42
42
  mode: TRACE_MODE.CONTINUOUS,
43
43
  screenshots: false,
44
+ // These are tests about what the viewer does with the checkpoints it is
45
+ // given, so the checkpoints are exactly the ones each test names. The
46
+ // base snapshot a continuous trace takes on its own is covered by the
47
+ // recorder's suite (issue #93).
48
+ initialCheckpoint: false,
44
49
  ...rest,
45
50
  });
46
51
  return { trace, page };