testaro 60.4.0 → 60.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.
@@ -32,7 +32,8 @@
32
32
 
33
33
  // Module to perform file operations.
34
34
  const fs = require('fs/promises');
35
- const {launch} = require(`${__dirname}/../run`);
35
+ //Modules to close and launch browsers.
36
+ const {browserClose, launch} = require(`${__dirname}/../run`);
36
37
  // Module to set operating-system constants.
37
38
  const os = require('os');
38
39
 
@@ -60,7 +61,7 @@ const doTestAct = async () => {
60
61
  const act = report.acts[actIndex];
61
62
  // Get the tool name.
62
63
  const {which} = act;
63
- // Launch a browser, navigate to the URL, and redefine the page export of the run module.
64
+ // Launch a browser, navigate to the URL, and update the page export of the run module.
64
65
  await launch(
65
66
  report,
66
67
  debug,
@@ -70,6 +71,8 @@ const doTestAct = async () => {
70
71
  );
71
72
  // If the launch aborted the job:
72
73
  if (report.jobData && report.jobData.aborted) {
74
+ // Close any existing browser.
75
+ await browserClose();
73
76
  // Save the revised report.
74
77
  const reportJSON = JSON.stringify(report);
75
78
  await fs.writeFile(reportPath, reportJSON);
@@ -78,37 +81,33 @@ const doTestAct = async () => {
78
81
  }
79
82
  // Otherwise, i.e. if the launch did not abort the job:
80
83
  else {
81
- // Get the redefined page.
84
+ // Get the updated page.
82
85
  const {page} = require('../run');
83
86
  // If it exists:
84
87
  if (page) {
85
88
  try {
86
- // If the page prevents the tool from testing:
87
- if (page.prevented) {
88
- // Report this.
89
- process.send('ERROR: Page prevented testing');
90
- }
91
- // Otherwise, i.e. if the page permits testing:
92
- else {
93
- // Wait for the act reporter to perform the specified tests of the tool.
94
- const actReport = await require(`../tests/${which}`).reporter(page, report, actIndex, 65);
95
- // Add the data and result to the act.
96
- act.data = actReport.data;
97
- act.result = actReport.result;
98
- // If the tool reported that the page prevented testing:
99
- if (act.data && act.data.prevented) {
100
- // Add prevention data to the job data.
101
- report.jobData.preventions[which] = act.data.error;
102
- }
103
- const reportJSON = JSON.stringify(report);
104
- // Save the revised report.
105
- await fs.writeFile(reportPath, reportJSON);
106
- // Send a completion message.
107
- process.send('Act completed');
89
+ // Make the act reporter perform the specified tests of the tool.
90
+ const actReport = await require(`../tests/${which}`).reporter(page, report, actIndex, 65);
91
+ // Add the data and result to the act.
92
+ act.data = actReport.data;
93
+ act.result = actReport.result;
94
+ // If the tool reported that the page prevented testing:
95
+ if (act.data && act.data.prevented) {
96
+ // Add prevention data to the job data.
97
+ report.jobData.preventions[which] = act.data.error;
108
98
  }
99
+ // Close any existing browser.
100
+ await browserClose();
101
+ const reportJSON = JSON.stringify(report);
102
+ // Save the revised report.
103
+ await fs.writeFile(reportPath, reportJSON);
104
+ // Send a completion message.
105
+ process.send('Act completed');
109
106
  }
110
107
  // If the tool invocation failed:
111
108
  catch(error) {
109
+ // Close any existing browser.
110
+ await browserClose();
112
111
  // Save the revised report.
113
112
  const reportJSON = JSON.stringify(report);
114
113
  await fs.writeFile(reportPath, reportJSON);
@@ -126,8 +125,10 @@ const doTestAct = async () => {
126
125
  act.data.error = 'No page';
127
126
  // Add prevention data to the job data.
128
127
  report.jobData.preventions[which] = act.data.error;
129
- // Save the revised report.
128
+ // Close any existing browser.
129
+ await browserClose();
130
130
  const reportJSON = JSON.stringify(report);
131
+ // Save the revised report.
131
132
  await fs.writeFile(reportPath, reportJSON);
132
133
  // Report this.
133
134
  const message = 'ERROR: No page';
package/procs/shoot.js ADDED
@@ -0,0 +1,51 @@
1
+ /*
2
+ © 2025 Jonathan Robert Pool. All rights reserved.
3
+ Licensed under the MIT License. See LICENSE file for details.
4
+ */
5
+
6
+ /*
7
+ shoot
8
+ Makes and saves as a PNG buffer file a full-page screenshot and returns the file path.
9
+ */
10
+
11
+ // IMPORTS
12
+
13
+ const fs = require('fs/promises');
14
+ const os = require('os');
15
+ const path = require('path');
16
+ const {PNG} = require('pngjs');
17
+ const {screenShot} = require('./screenShot');
18
+
19
+ // CONSTANTS
20
+
21
+ const tmpDir = os.tmpdir();
22
+
23
+ // FUNCTIONS
24
+
25
+ exports.shoot = async (page, index) => {
26
+ // Make and get a screenshot as a buffer.
27
+ let shot = await screenShot(page);
28
+ // If it succeeded:
29
+ if (shot.length) {
30
+ // Get the screenshot as an object representation of a PNG image.
31
+ let png = PNG.sync.read(shot);
32
+ shot = null;
33
+ const pngBuffer = PNG.sync.write(png);
34
+ png = null;
35
+ // Force garbage collection if available and if --expose-gc was a node option.
36
+ if (global.gc) {
37
+ global.gc();
38
+ }
39
+ const fileName = `testaro-shoot-${index}.png`;
40
+ const pngPath = path.join(tmpDir, fileName);
41
+ // Save the PNG buffer.
42
+ await fs.writeFile(pngPath, pngBuffer);
43
+ // Return the result.
44
+ return pngPath;
45
+ }
46
+ // Otherwise, i.e. if it failed:
47
+ else {
48
+ // Return this.
49
+ return '';
50
+ }
51
+ };
@@ -35,7 +35,7 @@ const agent = process.env.AGENT;
35
35
 
36
36
  // FUNCTIONS
37
37
 
38
- // Sends a notification to an observer.
38
+ // Sends a notice to an observer.
39
39
  exports.tellServer = (report, messageParams, logMessage) => {
40
40
  const {serverID} = report.sources;
41
41
  const observerURL = typeof serverID === 'number' ? process.env[`NETWATCH_URL_${serverID}_OBSERVE`] : '';
@@ -70,7 +70,7 @@ exports.visChange = async (page, options = {}) => {
70
70
  }
71
71
  catch(error) {
72
72
  return {
73
- success: false,
73
+ prevented: true,
74
74
  error: 'Hovering failed'
75
75
  };
76
76
  }
@@ -91,7 +91,7 @@ exports.visChange = async (page, options = {}) => {
91
91
  const changePercent = 100 * pixelChanges / (width * height);
92
92
  // Return this.
93
93
  return {
94
- success: true,
94
+ prevented: false,
95
95
  width,
96
96
  height,
97
97
  pixelChanges,
@@ -102,7 +102,6 @@ exports.visChange = async (page, options = {}) => {
102
102
  else {
103
103
  // Return this.
104
104
  return {
105
- success: false,
106
105
  prevented: true,
107
106
  error: 'Second screenshot failed'
108
107
  };
@@ -112,7 +111,6 @@ exports.visChange = async (page, options = {}) => {
112
111
  else {
113
112
  // Return this.
114
113
  return {
115
- success: false,
116
114
  prevented: true,
117
115
  error: 'First screenshot failed'
118
116
  };
package/run.js CHANGED
@@ -114,6 +114,8 @@ let actCount = 0;
114
114
  // Facts about the current act.
115
115
  let actIndex = 0;
116
116
  let browser;
117
+ let cleanupInProgress = false;
118
+ let browserCloseIntentional = false;
117
119
  let browserContext;
118
120
  let page;
119
121
  let report;
@@ -257,7 +259,6 @@ const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
257
259
  act.result.error ??= message;
258
260
  if (act.type === 'test') {
259
261
  act.data ??= {};
260
- act.data.success = false;
261
262
  act.data.prevented = true;
262
263
  act.data.error = message;
263
264
  // Add prevention data to the job data.
@@ -269,20 +270,20 @@ const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
269
270
  abortActs(report, actIndex);
270
271
  }
271
272
  };
272
- // Closes the current browser.
273
- const browserClose = async () => {
273
+ // Closes any current browser.
274
+ const browserClose = exports.browserClose = async () => {
275
+ // If a browser exists:
274
276
  if (browser) {
275
277
  browserCloseIntentional = true;
278
+ // Try to close all its contexts and ignore any messages that they are already closed.
276
279
  for (const context of browser.contexts()) {
277
280
  try {
278
281
  await context.close();
279
282
  }
280
283
  catch(error) {
281
- console.log(
282
- `ERROR trying to close context: ${error.message.slice(0, 200).replace(/\n.+/s, '')}`
283
- );
284
284
  }
285
285
  }
286
+ // Close the browser.
286
287
  await browser.close();
287
288
  browserCloseIntentional = false;
288
289
  browser = null;
@@ -303,7 +304,7 @@ const launch = exports.launch = async (
303
304
  report.target.url = url;
304
305
  // Create a browser of the specified or default type.
305
306
  const browserType = playwrightBrowsers[browserID];
306
- // Close the current browser, if any.
307
+ // Close any current browser.
307
308
  await browserClose();
308
309
  // Define browser options.
309
310
  const browserOptions = {
@@ -414,7 +415,7 @@ const launch = exports.launch = async (
414
415
  }
415
416
  });
416
417
  });
417
- // Replace the page with the first page (tab) of the context (window).
418
+ // Reassign the page variable to a new page (tab) of the context (window).
418
419
  page = await browserContext.newPage();
419
420
  // Wait until it is stable.
420
421
  await page.waitForLoadState('domcontentloaded', {timeout: 5000});
@@ -678,6 +679,7 @@ const launchSpecs = (act, report) => [
678
679
  // Performs the acts in a report and adds the results to the report.
679
680
  const doActs = async (report, opts = {}) => {
680
681
  const {acts} = report;
682
+ // Get the granular observation options, if any.
681
683
  const {onProgress = null, signal = null} = opts;
682
684
  // Get the standardization specification.
683
685
  const standard = report.standard || 'only';
@@ -696,7 +698,7 @@ const doActs = async (report, opts = {}) => {
696
698
  if (report.observe) {
697
699
  const whichParam = which ? `&which=${which}` : '';
698
700
  const messageParams = `act=${type}${whichParam}`;
699
- // If a progress callback has been provided:
701
+ // If a progress callback has been provided by a caller on this host:
700
702
  if (onProgress) {
701
703
  // Notify the observer of the act.
702
704
  try {
@@ -712,7 +714,7 @@ const doActs = async (report, opts = {}) => {
712
714
  }
713
715
  // Otherwise, i.e. if no progress callback has been provided:
714
716
  else {
715
- // Notify the observer of the act and log it.
717
+ // Notify the remote observer of the act and log it.
716
718
  tellServer(report, messageParams, message);
717
719
  }
718
720
  }
@@ -769,7 +771,7 @@ const doActs = async (report, opts = {}) => {
769
771
  actLaunchSpecs[1]
770
772
  );
771
773
  // If this failed:
772
- if (page.prevented) {
774
+ if (! page) {
773
775
  // Add this to the act.
774
776
  act.data ??= {};
775
777
  act.data.prevented = true;
@@ -1511,7 +1513,7 @@ const doActs = async (report, opts = {}) => {
1511
1513
  // For each browser ID/target URL class:
1512
1514
  for (const specString of Object.keys(launchSpecActs)) {
1513
1515
  const specs = specString.split('>');
1514
- // Launch a browser and navigate to the page.
1516
+ // Replace the browser and navigate to the URL.
1515
1517
  await launch(
1516
1518
  report,
1517
1519
  debug,
@@ -1519,18 +1521,18 @@ const doActs = async (report, opts = {}) => {
1519
1521
  specs[0],
1520
1522
  specs[1]
1521
1523
  );
1522
- // For each test act with the class:
1523
- for (const specActIndex of launchSpecActs[specString]) {
1524
- const act = report.acts[specActIndex];
1525
- // Initialize the standard result.
1526
- act.standardResult = {
1527
- totals: [0, 0, 0, 0],
1528
- instances: []
1529
- };
1530
- // Populate it.
1531
- standardize(act);
1532
- // If the launch and navigation succeeded:
1533
- if (! page.prevented) {
1524
+ // If the launch and navigation succeeded:
1525
+ if (page) {
1526
+ // For each test act in the class:
1527
+ for (const specActIndex of launchSpecActs[specString]) {
1528
+ const act = report.acts[specActIndex];
1529
+ // Initialize the standard result.
1530
+ act.standardResult = {
1531
+ totals: [0, 0, 0, 0],
1532
+ instances: []
1533
+ };
1534
+ // Populate it.
1535
+ standardize(act);
1534
1536
  // Add a box ID and a path ID to each of its standard instances if missing.
1535
1537
  for (const instance of act.standardResult.instances) {
1536
1538
  const elementID = await identify(instance, page);
@@ -1541,18 +1543,22 @@ const doActs = async (report, opts = {}) => {
1541
1543
  instance.pathID = elementID ? elementID.pathID : '';
1542
1544
  }
1543
1545
  };
1544
- }
1545
- // If the original-format result is not to be included in the report:
1546
- if (standard === 'only') {
1547
- // Remove it.
1548
- delete act.result;
1549
- }
1550
- };
1546
+ // If the original-format result is not to be included in the report:
1547
+ if (standard === 'only') {
1548
+ // Remove it.
1549
+ delete act.result;
1550
+ }
1551
+ };
1552
+ }
1553
+ // Otherwise, i.e. if the launch or navigation failed:
1554
+ else {
1555
+ console.log(`ERROR: Launch or navigation to standardize ${specString} acts failed`);
1556
+ }
1551
1557
  };
1552
- console.log('>>>> Standardization completed');
1558
+ // Close the last browser launched for standardization.
1559
+ await browserClose();
1560
+ console.log('Standardization completed');
1553
1561
  }
1554
- // Close the browser.
1555
- await browserClose();
1556
1562
  // Delete the temporary report file.
1557
1563
  await fs.rm(reportPath, {force: true});
1558
1564
  return report;
@@ -1598,7 +1604,7 @@ exports.doJob = async (job, opts = {}) => {
1598
1604
  process.exit();
1599
1605
  }
1600
1606
  });
1601
- // Perform the acts and get a report.
1607
+ // Perform the acts with any specified same-host observation options and get a report.
1602
1608
  report = await doActs(report, opts);
1603
1609
  // Add the end time and duration to the report.
1604
1610
  const endTime = new Date();
@@ -1623,10 +1629,6 @@ exports.doJob = async (job, opts = {}) => {
1623
1629
 
1624
1630
  // CLEANUP HANDLERS
1625
1631
 
1626
- // Track whether cleanup is in progress.
1627
- let cleanupInProgress = false;
1628
- let browserCloseIntentional = false;
1629
-
1630
1632
  // Force-kills any Playwright browser processes synchronously.
1631
1633
  const forceKillBrowsers = () => {
1632
1634
  if (cleanupInProgress) {
package/testaro/motion.js CHANGED
@@ -6,7 +6,7 @@
6
6
  /*
7
7
  motion
8
8
  This test reports motion in a page by comparing the first and last of the screenshots previously
9
- made by the shoot test.
9
+ made by the shoot0 and shoot1 tests.
10
10
 
11
11
  For minimal accessibility, standards require motion to be brief, or else stoppable by the user.
12
12
  But stopping motion can be difficult or impossible, and, by the time a user manages to stop
@@ -21,9 +21,18 @@
21
21
 
22
22
  // IMPORTS
23
23
 
24
- // Module to make a screenshot.
24
+ // Module to process files.
25
+ const fs = require('fs/promises');
26
+ // Module to get operating-system properties.
27
+ const os = require('os');
28
+ // Module to compare screenshots.
25
29
  const pixelmatch = require('pixelmatch').default;
26
- const {result} = require('../tests/testaro');
30
+ // Module to parse PNGs.
31
+ const {PNG} = require('pngjs');
32
+
33
+ // CONSTANTS
34
+
35
+ const tmpDir = os.tmpdir();
27
36
 
28
37
  // FUNCTIONS
29
38
 
@@ -33,29 +42,32 @@ exports.reporter = async page => {
33
42
  const data = {};
34
43
  const totals = [0, 0, 0, 0];
35
44
  const standardInstances = [];
36
- // Get the screenshots made by the shoot test.
37
- const shootResult = result ? result.shoot || {} : {};
38
- // If there are at least 2 of them:
39
- if (shootResult.pngs && shootResult.pngs.length > 1) {
40
- let {pngs} = shootResult;
41
- // Choose the first and last of them for comparison.
42
- const pngPair = [pngs[0], pngs[pngs.length - 1]];
43
- // Get their dimensions.
44
- const {width, height} = pngPair[0];
45
+ // Get the screenshot PNG buffers made by the shoot0 and shoot1 tests.
46
+ let shoot0PNGBuffer = await fs.readFile(`${tmpDir}/testaro-shoot-0.png`);
47
+ let shoot1PNGBuffer = await fs.readFile(`${tmpDir}/testaro-shoot-1.png`);
48
+ // Delete the buffers files.
49
+ await fs.unlink(`${tmpDir}/testaro-shoot-0.png`);
50
+ await fs.unlink(`${tmpDir}/testaro-shoot-1.png`);
51
+ // If both buffers exist:
52
+ if (shoot0PNGBuffer && shoot1PNGBuffer) {
53
+ // Parse them into PNG objects.
54
+ let shoot0PNG = PNG.sync.read(shoot0PNGBuffer);
55
+ let shoot1PNG = PNG.sync.read(shoot1PNGBuffer);
45
56
  // If their dimensions differ:
46
- if (width !== pngPair[1].width || height !== pngPair[1].height) {
57
+ if (shoot1PNG.width !== shoot0PNG.width || shoot1PNG.height !== shoot0PNG.height) {
47
58
  // Report this.
48
59
  data.prevented = true;
49
- data.error = 'Screenshots have differing dimensions';
60
+ data.error = 'Screenshot dimensions differ';
50
61
  }
51
62
  // Otherwise, i.e. if their dimensions are identical:
52
63
  else {
64
+ const {width, height} = shoot0PNG;
53
65
  // Get the count of differing pixels between the shots.
54
- const pixelChanges = pixelmatch(pngPair[0].data, pngPair[1].data, null, width, height);
66
+ const pixelChanges = pixelmatch(shoot0PNG.data, shoot1PNG.data, null, width, height);
55
67
  // Get the ratio of differing to all pixels as a percentage.
56
68
  const changePercent = 100 * pixelChanges / (width * height);
57
69
  // Free the memory used by screenshots.
58
- pngs = [];
70
+ shoot0PNG = shoot1PNG = shoot0PNGBuffer = shoot1PNGBuffer = null;
59
71
  // If any pixels were changed:
60
72
  if (pixelChanges) {
61
73
  // Get the ordinal severity from the fractional pixel change.
@@ -65,26 +77,31 @@ exports.reporter = async page => {
65
77
  // Get a summary standard instance.
66
78
  standardInstances.push({
67
79
  ruleID: 'motion',
68
- what: 'Content moves or changes without user request',
80
+ what: `Content moves or changes spontaneously (${changePercent}% of pixels changed)`,
69
81
  count: 1,
70
82
  ordinalSeverity,
71
83
  tagName: 'HTML',
72
84
  id: '',
73
85
  location: {
74
- doc: '',
75
- type: '',
76
- spec: ''
86
+ doc: 'dom',
87
+ type: 'box',
88
+ spec: {
89
+ x: 0,
90
+ y: 0,
91
+ width,
92
+ height
93
+ }
77
94
  },
78
- excerpt: ''
95
+ excerpt: '<html>…</html>'
79
96
  });
80
97
  }
81
98
  }
82
99
  }
83
- // Otherwise, i.e. if there are not at least 2 of them
100
+ // Otherwise, i.e. if they do not both exist:
84
101
  else {
85
102
  // Report this.
86
103
  data.prevented = true;
87
- data.error = 'Fewer than 2 screenshots recorded';
104
+ data.error = 'At least 1 screenshot missing';
88
105
  }
89
106
  // Return the result.
90
107
  return {
@@ -61,7 +61,7 @@ exports.reporter = async page => {
61
61
  delayBetween: 3000
62
62
  });
63
63
  // If the screenshots succeeded:
64
- if (data.success) {
64
+ if (! data.prevented) {
65
65
  // If any pixels were changed:
66
66
  if (data.pixelChanges) {
67
67
  // Get the ordinal severity from the fractional pixel change.
@@ -0,0 +1,26 @@
1
+ /*
2
+ © 2025 Jonathan Robert Pool. All rights reserved.
3
+ Licensed under the MIT License. See LICENSE file for details.
4
+ */
5
+
6
+ /*
7
+ shoot0
8
+ This test makes and saves the first of two screenshots.
9
+ */
10
+
11
+ // IMPORTS
12
+
13
+ const {shoot} = require('../procs/shoot');
14
+
15
+ // FUNCTIONS
16
+
17
+ exports.reporter = async page => {
18
+ // Make and save the first screenshot.
19
+ const pngPath = await shoot(page, 0);
20
+ // Return the file path or a failure result.
21
+ return {
22
+ data: {
23
+ prevented: ! pngPath
24
+ }
25
+ };
26
+ };
@@ -0,0 +1,26 @@
1
+ /*
2
+ © 2025 Jonathan Robert Pool. All rights reserved.
3
+ Licensed under the MIT License. See LICENSE file for details.
4
+ */
5
+
6
+ /*
7
+ shoot1
8
+ This test makes and saves the second of two screenshots.
9
+ */
10
+
11
+ // IMPORTS
12
+
13
+ const {shoot} = require('../procs/shoot');
14
+
15
+ // FUNCTIONS
16
+
17
+ exports.reporter = async page => {
18
+ // Make and save the second screenshot.
19
+ const pngPath = await shoot(page, 1);
20
+ // Return the file path or a failure result.
21
+ return {
22
+ data: {
23
+ prevented: ! pngPath
24
+ }
25
+ };
26
+ };
package/tests/testaro.js CHANGED
@@ -42,8 +42,8 @@ const fs = require('fs/promises');
42
42
  // Metadata of all rules in default execution order.
43
43
  const allRules = [
44
44
  {
45
- id: 'shoot',
46
- what: 'page screenshot',
45
+ id: 'shoot0',
46
+ what: 'first page screenshot',
47
47
  contaminator: false,
48
48
  timeOut: 5,
49
49
  defaultOn: true
@@ -350,8 +350,8 @@ const allRules = [
350
350
  defaultOn: true
351
351
  },
352
352
  {
353
- id: 'shoot',
354
- what: 'page screenshot',
353
+ id: 'shoot1',
354
+ what: 'second page screenshot',
355
355
  contaminator: false,
356
356
  timeOut: 5,
357
357
  defaultOn: true
@@ -455,7 +455,7 @@ process.on('unhandledRejection', reason => {
455
455
  console.error(`ERROR: Unhandled Promise Rejection (${reason})`);
456
456
  });
457
457
 
458
- // ######## FUNCTIONS
458
+ // FUNCTIONS
459
459
 
460
460
  // Conducts a JSON-defined test.
461
461
  const jsonTest = async (ruleID, ruleArgs) => {
@@ -503,7 +503,7 @@ exports.reporter = async (page, report, actIndex) => {
503
503
  rulesInvalid: [],
504
504
  ruleTestTimes: {}
505
505
  };
506
- const result = exports.result = {};
506
+ const result = {};
507
507
  const allRuleIDs = allRules.map(rule => rule.id);
508
508
  // If the rule specification is invalid:
509
509
  if (! (
@@ -559,6 +559,7 @@ exports.reporter = async (page, report, actIndex) => {
559
559
  // Report crashes and disconnections during this test.
560
560
  let crashHandler;
561
561
  let disconnectHandler;
562
+ // Get the current browser.
562
563
  const {browser} = require('../run');
563
564
  if (page && ! page.isClosed()) {
564
565
  crashHandler = () => {
@@ -631,7 +632,10 @@ exports.reporter = async (page, report, actIndex) => {
631
632
  // Prevent a retry of the test.
632
633
  testSuccess = true;
633
634
  // If testing is to stop after a failure and the page failed the test:
634
- if (stopOnFail && ruleOrTimeoutReport.totals.some(total => total)) {
635
+ if (
636
+ stopOnFail
637
+ && ruleOrTimeoutReport.totals
638
+ && ruleOrTimeoutReport.totals.some(total => total)) {
635
639
  // Stop testing.
636
640
  break;
637
641
  }
@@ -693,9 +697,11 @@ exports.reporter = async (page, report, actIndex) => {
693
697
  // Clear the error listeners.
694
698
  if (page && ! page.isClosed() && crashHandler) {
695
699
  page.off('crash', crashHandler);
700
+ crashHandler = null;
696
701
  }
697
702
  if (browser && disconnectHandler) {
698
703
  browser.off('disconnected', disconnectHandler);
704
+ disconnectHandler = null;
699
705
  }
700
706
  }
701
707
  // Otherwise, i.e. if the rule is undefined or doubly defined:
@@ -704,10 +710,22 @@ exports.reporter = async (page, report, actIndex) => {
704
710
  data.rulesInvalid.push(rule);
705
711
  console.log(`ERROR: Rule ${rule.id} not validly defined`);
706
712
  // Clear the crash listener.
707
- if (page && ! page.isClosed()) {
713
+ if (page && ! page.isClosed() && crashHandler) {
708
714
  page.off('crash', crashHandler);
715
+ crashHandler = null;
716
+ }
717
+ if (browser && disconnectHandler) {
718
+ browser.off('disconnected', disconnectHandler);
719
+ disconnectHandler = null;
720
+ }
721
+ }
722
+ // Force a garbage collection.
723
+ try {
724
+ if (global.gc) {
725
+ global.gc();
709
726
  }
710
727
  }
728
+ catch(error) {}
711
729
  };
712
730
  // Record the test times in descending order.
713
731
  testTimes.sort((a, b) => b[1] - a[1]).forEach(pair => {
package/tests/wave.js CHANGED
@@ -75,9 +75,10 @@ exports.reporter = async (page, report, actIndex) => {
75
75
  });
76
76
  // When they arrive:
77
77
  response.on('end', async () => {
78
+ let actResult = {};
78
79
  // Delete unnecessary properties.
79
80
  try {
80
- const actResult = JSON.parse(rawReport);
81
+ actResult = JSON.parse(rawReport);
81
82
  const {categories, statistics} = actResult;
82
83
  delete categories.feature;
83
84
  delete categories.structure;
@@ -141,7 +142,7 @@ exports.reporter = async (page, report, actIndex) => {
141
142
  catch(error) {
142
143
  data.prevented = true;
143
144
  data.error = error.message;
144
- resolve(result);
145
+ resolve(actResult);
145
146
  };
146
147
  });
147
148
  }