Skip to content
This repository was archived by the owner on Oct 3, 2024. It is now read-only.

Commit a4cab99

Browse files
Support for testable errors codes for all user facing errors
The `error.name` property can be used to determine what caused the error. Some error messagers have also been improved and some missing tests were added
1 parent a4521e8 commit a4cab99

27 files changed

Lines changed: 283 additions & 97 deletions

File tree

core/lib/background/RunnerScriptParent.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const EventEmitter = require('events').EventEmitter;
44
const JSONBird = require('jsonbird');
55

6+
const {SCRIPT_EXECUTION_TIMEOUT_ERROR} = require('../../../lib/scriptErrors');
67
const log = require('../../../lib/logger')({hostname: 'background', MODULE: 'core/background/RunnerScriptParent'});
78
const coreMethods = require('./coreMethods');
89
const loadModule = require('./loadModule');
@@ -47,7 +48,7 @@ class RunnerScriptParent {
4748
return this[PRIVATE].run();
4849
}
4950

50-
async stop(reason = 'unknown') {
51+
async stop(reason) {
5152
return this[PRIVATE].stop(reason);
5253
}
5354

@@ -172,7 +173,10 @@ class RunnerScriptParentPrivate {
172173
const {runTimeoutMs, stackFileName} = this;
173174
this.scriptTimeoutTimer = setTimeout(() => {
174175
this.scriptTimeoutTimer = 0;
175-
this.stop(`Script execution timed out after ${runTimeoutMs / 1000} seconds`)
176+
this.stop({
177+
name: SCRIPT_EXECUTION_TIMEOUT_ERROR,
178+
message: `Script execution timed out after ${runTimeoutMs / 1000} seconds`,
179+
})
176180
.catch(err => log.error({err}, 'Stopping script (after script timeout) failed'));
177181
}, runTimeoutMs);
178182

@@ -197,7 +201,7 @@ class RunnerScriptParentPrivate {
197201
await this.emitRunScriptResult(runScriptResult);
198202
scriptResult.timing.endNow();
199203
log.info({err: runScriptResult.scriptError}, 'Worker completed runScript command');
200-
await this.stop('Normal script completion');
204+
await this.stop({message: 'Normal script completion'});
201205
}
202206
finally { // only emit runEnd if runStart was emitted
203207
// eslint-disable-next-line camelcase, no-undef
@@ -239,15 +243,15 @@ class RunnerScriptParentPrivate {
239243
}
240244
}
241245

242-
async stop(reason) {
246+
async stop({name, message} = {}) {
243247
if (this.sentStopCommand) {
244248
return false;
245249
}
246250

247251
this.sentStopCommand = true;
248252

249253
await this.rpcCall('core.stopScript', {
250-
reason: reason,
254+
reason: {name, message},
251255
});
252256
return true;
253257
}

core/lib/background/scratchpadMethods.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ module.exports = ({browserTabId, browserTabs, browserDownloads, rpc, scratchpadR
3030
setRunState({state: 'stopping'});
3131

3232
try {
33-
await script.stop(reason);
33+
await script.stop({message: reason});
3434
}
3535
finally {
3636
setRunState({state: 'stopped'});

core/lib/script-env/RunnerScript.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,12 @@ class RunnerScriptPrivate {
158158
log.info('Emitted core.runEnd...');
159159
}
160160

161-
async stop(reason) {
162-
log.info({reason}, 'Stopping...');
163-
this.stopPromiseReject(Error(`Script stopped: ${reason}`));
161+
async stop({name = 'Error', message = ''}) {
162+
log.info({name, message}, 'Stopping...');
163+
const err = new Error(`Script stopped: ${message}`);
164+
err.name = name;
165+
this.stopReason = err;
166+
this.stopPromiseReject(err);
164167
}
165168

166169
compileScript(scriptContent, stackFileName) {

lib/RequestModificationPatterns.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
const {illegalArgumentError} = require('./scriptErrors');
23
const log = require('./logger')({
34
hostname: 'background',
45
MODULE: 'RequestModificationPatterns',
@@ -18,12 +19,12 @@ class RequestModificationPatterns {
1819

1920
add(patterns, data = null) {
2021
if (!Array.isArray(patterns) || !patterns.length) {
21-
throw new TypeError('add(): Argument `patterns` must be an non empty array of strings');
22+
throw illegalArgumentError('RequestModificationPatterns.add(): Argument `patterns` must be an non empty array of strings');
2223
}
2324

2425
for (const pattern of patterns) {
2526
if (typeof pattern !== 'string') {
26-
throw new TypeError('add(): Argument `patterns` must be an non empty array of strings');
27+
throw illegalArgumentError('RequestModificationPatterns.add(): Argument `patterns` must be an non empty array of strings');
2728
}
2829
}
2930

lib/domUtilities.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
const {illegalArgumentError} = require('./scriptErrors');
23
const findPropertyInChain = require('./findPropertyInChain');
34

45
// These constants are repeated here so that we can access them even if there is no DOM (yet)
@@ -68,7 +69,7 @@ const isNode = object => {
6869
*/
6970
const assertIsNode = (object, message = '') => {
7071
if (!isNode(object)) {
71-
throw new TypeError(`${message}Expected value to be a DOM Node`);
72+
throw illegalArgumentError(`${message}Expected value to be a DOM Node`);
7273
}
7374
};
7475

@@ -86,7 +87,7 @@ const assertIsNodeType = (object, nodeType, message = '') => {
8687
if (object.nodeType !== nodeType) {
8788
const names = NODE_TYPE_CONSTRUCTOR_NAME;
8889
const fullMessage = `${message}Expected value to be a ${names[nodeType]} Node instead of a ${names[object.nodeType]} Node`;
89-
throw new TypeError(fullMessage);
90+
throw illegalArgumentError(fullMessage);
9091
}
9192
};
9293

lib/scriptErrors.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
'use strict';
2+
3+
// (all errors which are visible to the runner script made by the user under normal conditions)
4+
5+
/**
6+
* An argument passed to one of the api functions or setters is incompatible with the type expected by that function
7+
*/
8+
const ILLEGAL_ARGUMENT_ERROR = 'Openrunner:IllegalArgumentError';
9+
10+
/**
11+
* An api function has been called at an inappropriate time
12+
*/
13+
const ILLEGAL_STATE_ERROR = 'Openrunner:IllegalStateError';
14+
15+
/**
16+
* Unable to navigate to the specified URL using tab.navigate()
17+
*/
18+
const NAVIGATE_ERROR = 'Openrunner:NavigateError';
19+
20+
/**
21+
* And assertion made by the script has failed.
22+
*/
23+
const ASSERTION_ERROR = 'Openrunner:AssertionError';
24+
25+
/**
26+
* The script is waiting for a condition in the DOM (e.g. a specific DOM Element to be present) which has been
27+
* aborted because it took too long.
28+
*/
29+
const DOM_WAIT_TIMEOUT_ERROR = 'BluefoxTimeoutError';
30+
31+
/**
32+
* The script is waiting for a new page to be navigated too, which has been aborted because it took too long.
33+
*/
34+
const NEW_PAGE_WAIT_TIMEOUT_ERROR = 'Openrunner:NewPageWaitTimeoutError';
35+
36+
37+
/**
38+
* An active transaction has been aborted because the script is stopping.
39+
*/
40+
const TRANSACTION_ABORTED_ERROR = 'Openrunner:TransactionAbortedError';
41+
42+
/**
43+
* An active content script (e.g. `tab.run()`)has been aborted because the page is navigating away,
44+
* the tab is closing, or the script is stopping.
45+
*/
46+
47+
const CONTENT_SCRIPT_ABORTED_ERROR = 'Openrunner:ContentScriptAbortedError';
48+
49+
/**
50+
* The Openrunner script has been running longer than its configured timeout limit and has been aborted.
51+
* For example:
52+
* 'Openrunner-Script-Timeout: 10s'
53+
*/
54+
const SCRIPT_EXECUTION_TIMEOUT_ERROR = 'Openrunner:ScriptExecutionTimeoutError';
55+
56+
const createErrorShorthand = name => (message, cause = null) => {
57+
const err = new Error(message);
58+
err.name = name;
59+
err.cause = cause;
60+
return err;
61+
};
62+
63+
const translateRpcErrorName = err => {
64+
const match = /RPCRequestError<(Openrunner:.*?)>/.exec(err && err.name);
65+
if (match) {
66+
err.name = match[1];
67+
}
68+
throw err;
69+
};
70+
71+
module.exports = {
72+
ILLEGAL_ARGUMENT_ERROR,
73+
ILLEGAL_STATE_ERROR,
74+
NAVIGATE_ERROR,
75+
ASSERTION_ERROR,
76+
DOM_WAIT_TIMEOUT_ERROR,
77+
NEW_PAGE_WAIT_TIMEOUT_ERROR,
78+
TRANSACTION_ABORTED_ERROR,
79+
CONTENT_SCRIPT_ABORTED_ERROR,
80+
SCRIPT_EXECUTION_TIMEOUT_ERROR,
81+
illegalArgumentError: createErrorShorthand(ILLEGAL_ARGUMENT_ERROR),
82+
illegalStateError: createErrorShorthand(ILLEGAL_STATE_ERROR),
83+
navigateError: createErrorShorthand(NAVIGATE_ERROR),
84+
assertionError: createErrorShorthand(ASSERTION_ERROR),
85+
newPageWaitTimeoutError: createErrorShorthand(NEW_PAGE_WAIT_TIMEOUT_ERROR),
86+
transactionAbortedError: createErrorShorthand(TRANSACTION_ABORTED_ERROR),
87+
contentScriptAbortedError: createErrorShorthand(CONTENT_SCRIPT_ABORTED_ERROR),
88+
scriptExecutionTimeoutError: createErrorShorthand(SCRIPT_EXECUTION_TIMEOUT_ERROR),
89+
translateRpcErrorName,
90+
};

runner-modules/eventSimulation/lib/content/keyboard.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
const {illegalArgumentError} = require('../../../../lib/scriptErrors');
23
const findPropertyInChain = require('../../../../lib/findPropertyInChain');
34
const {getOwnerDocument, getDocumentWindow, assertIsNodeType, ELEMENT_NODE} = require('../../../../lib/domUtilities');
45
const {parseKeyIdentifiers, SHIFT_KEY} = require('./keys');
@@ -115,7 +116,7 @@ const keyboardTextInput = async (element, keyIdentifiers, options = {}) => {
115116
const isElementContentEditable = element.isContentEditable;
116117

117118
if (!isElementTextValueControl && !isElementContentEditable) {
118-
throw new TypeError(
119+
throw illegalArgumentError(
119120
`keyboardTextInput(): (${element.nodeName} ${element.type || ''}) is not a valid element for text input`
120121
);
121122
}

runner-modules/eventSimulation/lib/content/keys.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
const {illegalArgumentError} = require('../../../../lib/scriptErrors');
23

34
const DOM_KEY_LOCATION_STANDARD = 0;
45
const DOM_KEY_LOCATION_LEFT = 1;
@@ -240,7 +241,7 @@ const parseKeyIdentifiers = identifiers => {
240241
});
241242

242243
if (invalidKeys.length) {
243-
throw new TypeError(`Invalid Argument: The following keys are not supported: "${invalidKeys.join('", "')}"`);
244+
throw illegalArgumentError(`The following keys are not supported: "${invalidKeys.join('", "')}"`);
244245
}
245246

246247
return keyEntries;

runner-modules/requestBlocking/lib/script-env/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
const {illegalArgumentError} = require('../../../../lib/scriptErrors');
23

34
const parsePatternArg = patternsArg => {
45
const patterns = Array.isArray(patternsArg)
@@ -7,7 +8,7 @@ const parsePatternArg = patternsArg => {
78

89
for (const value of patterns) {
910
if (typeof value !== 'string') {
10-
throw Error('Invalid patterns argument');
11+
throw illegalArgumentError('requestBlocking: Invalid patterns argument');
1112
}
1213
}
1314
return patterns;

runner-modules/requestModification/lib/script-env/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
const {illegalArgumentError} = require('../../../../lib/scriptErrors');
23

34
const parsePatternArg = patternsArg => {
45
const patterns = Array.isArray(patternsArg)
@@ -7,7 +8,7 @@ const parsePatternArg = patternsArg => {
78

89
for (const value of patterns) {
910
if (typeof value !== 'string') {
10-
throw Error('Invalid patterns argument');
11+
throw illegalArgumentError('requestModification: Invalid patterns argument');
1112
}
1213
}
1314
return patterns;
@@ -17,7 +18,7 @@ const parseHeadersArg = headersArg => {
1718
const headers = [];
1819
for (const [name, value] of Object.entries(headersArg)) {
1920
if (value !== null && typeof value !== 'string') {
20-
throw Error(`Invalid header value for ${name}`);
21+
throw illegalArgumentError(`requestModification: Invalid header value for ${name}`);
2122
}
2223

2324
headers.push([name, value]);

0 commit comments

Comments
 (0)