testaro 8.4.7 → 9.0.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 +94 -155
- package/{commands.js → actSpecs.js} +1 -1
- package/call.js +35 -13
- package/package.json +2 -3
- package/run.js +155 -181
- package/samples/00000-ts18-example.json +3 -3
- package/validation/executors/run.js +40 -0
- package/validation/executors/watchDir.js +7 -6
- package/validation/executors/watchNet.js +5 -6
- package/validation/jobs/done/README.md +3 -0
- package/validation/jobs/{00000-simple-example.json → todo/00000-simple-example.json} +6 -7
- package/validation/jobs/{README.md → todo/README.md} +0 -0
- package/validation/tests/jobs/allHidden.json +3 -2
- package/validation/tests/jobs/bulk.json +3 -2
- package/validation/tests/jobs/docType.json +3 -2
- package/validation/tests/jobs/elements.json +3 -2
- package/validation/tests/jobs/embAc.json +3 -2
- package/validation/tests/jobs/filter.json +3 -2
- package/validation/tests/jobs/focAll.json +3 -2
- package/validation/tests/jobs/focInd.json +3 -2
- package/validation/tests/jobs/focOp.json +3 -2
- package/validation/tests/jobs/focVis.json +3 -2
- package/validation/tests/jobs/hover.json +3 -2
- package/validation/tests/jobs/labClash.json +3 -2
- package/validation/tests/jobs/linkTo.json +3 -2
- package/validation/tests/jobs/linkUl.json +3 -2
- package/validation/tests/jobs/menuNav.json +3 -2
- package/validation/tests/jobs/miniText.json +3 -2
- package/validation/tests/jobs/motion.json +3 -2
- package/validation/tests/jobs/nonTable.json +3 -2
- package/validation/tests/jobs/radioSet.json +3 -2
- package/validation/tests/jobs/role.json +3 -2
- package/validation/tests/jobs/styleDiff.json +3 -2
- package/validation/tests/jobs/tabNav.json +3 -2
- package/validation/tests/jobs/textNodes.json +3 -2
- package/validation/tests/jobs/title.json +3 -2
- package/validation/tests/jobs/titledEl.json +3 -2
- package/validation/tests/jobs/zIndex.json +3 -2
- package/validation/validateTest.js +3 -8
- package/validation/watch/{README.md → done/README.md} +0 -0
- package/validation/watch/todo/README.md +3 -0
- package/watch.js +39 -49
- package/high.js +0 -60
- package/validation/executors/high.js +0 -52
- package/validation/executors/low.js +0 -53
package/run.js
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
// Module to keep secrets.
|
|
9
9
|
require('dotenv').config();
|
|
10
|
-
// Requirements for
|
|
11
|
-
const {
|
|
10
|
+
// Requirements for acts.
|
|
11
|
+
const {actSpecs} = require('./actSpecs');
|
|
12
|
+
// Playwright package.
|
|
13
|
+
const playwright = require('playwright');
|
|
12
14
|
|
|
13
15
|
// ########## CONSTANTS
|
|
14
16
|
|
|
@@ -16,7 +18,6 @@ const {commands} = require('./commands');
|
|
|
16
18
|
const debug = process.env.DEBUG === 'true';
|
|
17
19
|
// Set WAITS environment variable to a positive number to insert delays (in ms).
|
|
18
20
|
const waits = Number.parseInt(process.env.WAITS) || 0;
|
|
19
|
-
const urlInject = process.env.URL_INJECT || 'yes';
|
|
20
21
|
// CSS selectors for targets of moves.
|
|
21
22
|
const moves = {
|
|
22
23
|
button: 'button, [role=button], input[type=submit]',
|
|
@@ -66,20 +67,6 @@ const tests = {
|
|
|
66
67
|
wave: 'WAVE',
|
|
67
68
|
zIndex: 'z indexes'
|
|
68
69
|
};
|
|
69
|
-
// Tests that may change the DOM.
|
|
70
|
-
const domChangers = new Set([
|
|
71
|
-
'axe',
|
|
72
|
-
'continuum',
|
|
73
|
-
'focAll',
|
|
74
|
-
'focInd',
|
|
75
|
-
'focOp',
|
|
76
|
-
'hover',
|
|
77
|
-
'htmlcs',
|
|
78
|
-
'ibm',
|
|
79
|
-
'menuNav',
|
|
80
|
-
'textNodes',
|
|
81
|
-
'wave'
|
|
82
|
-
]);
|
|
83
70
|
// Browser types available in PlayWright.
|
|
84
71
|
const browserTypeNames = {
|
|
85
72
|
'chromium': 'Chrome',
|
|
@@ -187,24 +174,24 @@ const hasSubtype = (variable, subtype) => {
|
|
|
187
174
|
return true;
|
|
188
175
|
}
|
|
189
176
|
};
|
|
190
|
-
// Validates
|
|
191
|
-
const
|
|
192
|
-
// Identify the type of the
|
|
193
|
-
const type =
|
|
177
|
+
// Validates an act.
|
|
178
|
+
const isValidAct = act => {
|
|
179
|
+
// Identify the type of the act.
|
|
180
|
+
const type = act.type;
|
|
194
181
|
// If the type exists and is known:
|
|
195
|
-
if (type &&
|
|
182
|
+
if (type && actSpecs.etc[type]) {
|
|
196
183
|
// Copy the validator of the type for possible expansion.
|
|
197
|
-
const validator = Object.assign({},
|
|
184
|
+
const validator = Object.assign({}, actSpecs.etc[type][1]);
|
|
198
185
|
// If the type is test:
|
|
199
186
|
if (type === 'test') {
|
|
200
187
|
// Identify the test.
|
|
201
|
-
const testName =
|
|
188
|
+
const testName = act.which;
|
|
202
189
|
// If one was specified and is known:
|
|
203
190
|
if (testName && tests[testName]) {
|
|
204
191
|
// If it has special properties:
|
|
205
|
-
if (
|
|
192
|
+
if (actSpecs.tests[testName]) {
|
|
206
193
|
// Expand the validator by adding them.
|
|
207
|
-
Object.assign(validator,
|
|
194
|
+
Object.assign(validator, actSpecs.tests[testName][1]);
|
|
208
195
|
}
|
|
209
196
|
}
|
|
210
197
|
// Otherwise, i.e. if no or an unknown test was specified:
|
|
@@ -213,22 +200,22 @@ const isValidCommand = command => {
|
|
|
213
200
|
return false;
|
|
214
201
|
}
|
|
215
202
|
}
|
|
216
|
-
// Return whether the
|
|
203
|
+
// Return whether the act is valid.
|
|
217
204
|
return Object.keys(validator).every(property => {
|
|
218
205
|
if (property === 'name') {
|
|
219
206
|
return true;
|
|
220
207
|
}
|
|
221
208
|
else {
|
|
222
209
|
const vP = validator[property];
|
|
223
|
-
const
|
|
224
|
-
// If it is optional and omitted or present and valid:
|
|
225
|
-
const optAndNone = ! vP[0] && !
|
|
226
|
-
const
|
|
227
|
-
return optAndNone ||
|
|
210
|
+
const aP = act[property];
|
|
211
|
+
// If it is optional and omitted or is present and valid:
|
|
212
|
+
const optAndNone = ! vP[0] && ! aP;
|
|
213
|
+
const isValidAct = aP !== undefined && hasType(aP, vP[1]) && hasSubtype(aP, vP[2]);
|
|
214
|
+
return optAndNone || isValidAct;
|
|
228
215
|
}
|
|
229
216
|
});
|
|
230
217
|
}
|
|
231
|
-
// Otherwise, i.e. if the
|
|
218
|
+
// Otherwise, i.e. if the act has an unknown or no type:
|
|
232
219
|
else {
|
|
233
220
|
// Return invalidity.
|
|
234
221
|
return false;
|
|
@@ -238,42 +225,60 @@ const isValidCommand = command => {
|
|
|
238
225
|
const isValidReport = report => {
|
|
239
226
|
if (report) {
|
|
240
227
|
// Return whether the report is valid.
|
|
241
|
-
const {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
[job, 'report has job property'],
|
|
245
|
-
[acts, 'report has acts property'],
|
|
246
|
-
[jobData, 'report has jobData property'],
|
|
247
|
-
[id, 'job has id property'],
|
|
248
|
-
[what, 'job has what property'],
|
|
249
|
-
[typeof strict === 'boolean', 'job has true or false strict property'],
|
|
250
|
-
[commands, 'job has commands property'],
|
|
251
|
-
[sources, 'job has sources property'],
|
|
252
|
-
[jobCreationTime, 'job has jobCreationTime property'],
|
|
253
|
-
[timeStamp, 'job has timeStamp property'],
|
|
254
|
-
[Array.isArray(acts), 'acts has array value'],
|
|
255
|
-
[typeof id === 'string', 'id has string value'],
|
|
256
|
-
[typeof what === 'string', 'what has string value'],
|
|
257
|
-
[Array.isArray(commands), 'commands has array value'],
|
|
258
|
-
[commands[0].type, 'first command has type property'],
|
|
259
|
-
[commands[0].type === 'launch', 'first command has launch type'],
|
|
260
|
-
[commands.length > 1, 'command count greater than 1'],
|
|
261
|
-
[commands[1].type, 'second command has type property'],
|
|
262
|
-
[commands[1].type === 'url', 'second command has url type'],
|
|
263
|
-
[commands[1].which, 'second command has which property'],
|
|
264
|
-
[isURL(commands[1].which), 'second command which property has URL value'],
|
|
265
|
-
[commands.every(command => isValidCommand(command)), 'every command is valid'],
|
|
266
|
-
[sources && typeof sources.script === 'string', 'sources has script property with string value'],
|
|
267
|
-
[sources && sources.host, 'sources has host property']
|
|
268
|
-
];
|
|
269
|
-
const invalidityIndex = criteria.findIndex(criterion => ! criterion[0]);
|
|
270
|
-
if (invalidityIndex > -1) {
|
|
271
|
-
console.log(`ERROR: report fails “${criteria[invalidityIndex][1]}” requirement`);
|
|
228
|
+
const {id, what, strict, timeLimit, acts, sources, creationTime, timeStamp} = report;
|
|
229
|
+
if (! id || typeof id !== 'string') {
|
|
230
|
+
return 'Bad report ID';
|
|
272
231
|
}
|
|
273
|
-
|
|
232
|
+
if (! what || typeof what !== 'string') {
|
|
233
|
+
return 'Bad report what';
|
|
234
|
+
}
|
|
235
|
+
if (! strict || typeof strict !== 'boolean') {
|
|
236
|
+
return 'Bad report strict';
|
|
237
|
+
}
|
|
238
|
+
if (! timeLimit || typeof timeLimit !== 'number' || timeLimit < 1) {
|
|
239
|
+
return 'Bad report time limit';
|
|
240
|
+
}
|
|
241
|
+
if (! acts || ! Array.isArray(acts) || acts.length < 2) {
|
|
242
|
+
return 'Bad report acts';
|
|
243
|
+
}
|
|
244
|
+
if (! acts.every(act => act.type && typeof act.type === 'string')) {
|
|
245
|
+
return 'Act with no type';
|
|
246
|
+
}
|
|
247
|
+
if (acts[0].type !== 'launch') {
|
|
248
|
+
return 'First act type not launch';
|
|
249
|
+
}
|
|
250
|
+
if (acts[1].type !== 'url') {
|
|
251
|
+
return 'Second act type not url';
|
|
252
|
+
}
|
|
253
|
+
if (! ['chromium', 'webkit', 'firefox'].includes(acts[0].which)) {
|
|
254
|
+
return 'Bad first act which';
|
|
255
|
+
}
|
|
256
|
+
if (! acts[1].which || typeof acts[1].which !== 'string' || ! isURL(acts[1].which)) {
|
|
257
|
+
return 'Second act which not a URL';
|
|
258
|
+
}
|
|
259
|
+
if (acts.some(act => ! isValidAct(act))) {
|
|
260
|
+
return 'Not all acts valid';
|
|
261
|
+
}
|
|
262
|
+
if (! sources || typeof sources !== 'object') {
|
|
263
|
+
return 'Bad report sources';
|
|
264
|
+
}
|
|
265
|
+
if (typeof sources.script !== 'string') {
|
|
266
|
+
return 'Bad sources script';
|
|
267
|
+
}
|
|
268
|
+
if (
|
|
269
|
+
! creationTime
|
|
270
|
+
|| typeof creationTime !== 'string'
|
|
271
|
+
|| ! /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(creationTime)
|
|
272
|
+
) {
|
|
273
|
+
return 'bad job creation time';
|
|
274
|
+
}
|
|
275
|
+
if (! timeStamp || typeof timeStamp !== 'string') {
|
|
276
|
+
return 'bad report timestamp';
|
|
277
|
+
}
|
|
278
|
+
return '';
|
|
274
279
|
}
|
|
275
280
|
else {
|
|
276
|
-
return
|
|
281
|
+
return 'no report';
|
|
277
282
|
}
|
|
278
283
|
};
|
|
279
284
|
|
|
@@ -295,7 +300,7 @@ const browserClose = async () => {
|
|
|
295
300
|
const errorStart = error => error.message.replace(/\n.+/s, '');
|
|
296
301
|
// Launches a browser.
|
|
297
302
|
const launch = async (report, typeName, lowMotion = false) => {
|
|
298
|
-
const browserType =
|
|
303
|
+
const browserType = playwright[typeName];
|
|
299
304
|
// If the specified browser type exists:
|
|
300
305
|
if (browserType) {
|
|
301
306
|
// Close the current browser, if any.
|
|
@@ -604,6 +609,18 @@ const goTo = async (report, page, url, timeout, waitUntil, isStrict) => {
|
|
|
604
609
|
};
|
|
605
610
|
// Recursively performs the acts in a report.
|
|
606
611
|
const doActs = async (report, actIndex, page) => {
|
|
612
|
+
// Quits and reports the performance being aborted.
|
|
613
|
+
const abortActs = async () => {
|
|
614
|
+
// Add data on the aborted act to the report.
|
|
615
|
+
report.jobData.abortTime = nowString();
|
|
616
|
+
report.jobData.abortedAct = actIndex;
|
|
617
|
+
// Prevent performance of additional acts.
|
|
618
|
+
actIndex = -2;
|
|
619
|
+
// Report this.
|
|
620
|
+
console.log('ERROR: Job aborted');
|
|
621
|
+
// Stop the action until the user resumes it.
|
|
622
|
+
await page.pause();
|
|
623
|
+
};
|
|
607
624
|
process.on('message', message => {
|
|
608
625
|
if (message === 'interrupt') {
|
|
609
626
|
console.log('ERROR: Terminal interrupted doActs');
|
|
@@ -611,18 +628,18 @@ const doActs = async (report, actIndex, page) => {
|
|
|
611
628
|
}
|
|
612
629
|
});
|
|
613
630
|
const {acts} = report;
|
|
614
|
-
// If any more
|
|
631
|
+
// If any more acts are to be performed:
|
|
615
632
|
if (actIndex > -1 && actIndex < acts.length) {
|
|
616
|
-
// Identify the
|
|
633
|
+
// Identify the act to be performed.
|
|
617
634
|
const act = acts[actIndex];
|
|
618
635
|
// If it is valid:
|
|
619
|
-
if (
|
|
636
|
+
if (isValidAct(act)) {
|
|
620
637
|
const whichSuffix = act.which ? ` (${act.which})` : '';
|
|
621
638
|
console.log(`>>>> ${act.type}${whichSuffix}`);
|
|
622
|
-
// Increment the count of
|
|
639
|
+
// Increment the count of acts performed.
|
|
623
640
|
actCount++;
|
|
624
641
|
act.startTime = Date.now();
|
|
625
|
-
// If the
|
|
642
|
+
// If the act is an index changer:
|
|
626
643
|
if (act.type === 'next') {
|
|
627
644
|
const condition = act.if;
|
|
628
645
|
const logSuffix = condition.length === 3 ? ` ${condition[1]} ${condition[2]}` : '';
|
|
@@ -641,9 +658,9 @@ const doActs = async (report, actIndex, page) => {
|
|
|
641
658
|
};
|
|
642
659
|
// If the condition is true:
|
|
643
660
|
if (truth[1]) {
|
|
644
|
-
// If the performance of
|
|
661
|
+
// If the performance of acts is to stop:
|
|
645
662
|
if (act.jump === 0) {
|
|
646
|
-
//
|
|
663
|
+
// Quit.
|
|
647
664
|
actIndex = -2;
|
|
648
665
|
}
|
|
649
666
|
// Otherwise, if there is a numerical jump:
|
|
@@ -651,14 +668,14 @@ const doActs = async (report, actIndex, page) => {
|
|
|
651
668
|
// Set the act index accordingly.
|
|
652
669
|
actIndex += act.jump - 1;
|
|
653
670
|
}
|
|
654
|
-
// Otherwise, if there is a named next
|
|
671
|
+
// Otherwise, if there is a named next act:
|
|
655
672
|
else if (act.next) {
|
|
656
673
|
// Set the new index accordingly, or stop if it does not exist.
|
|
657
674
|
actIndex = acts.map(act => act.name).indexOf(act.next) - 1;
|
|
658
675
|
}
|
|
659
676
|
}
|
|
660
677
|
}
|
|
661
|
-
// Otherwise, if the
|
|
678
|
+
// Otherwise, if the act is a launch:
|
|
662
679
|
else if (act.type === 'launch') {
|
|
663
680
|
// Launch the specified browser, creating a browser context and a page in it.
|
|
664
681
|
await launch(report, act.which, act.lowMotion ? 'reduce' : 'no-preference');
|
|
@@ -667,13 +684,13 @@ const doActs = async (report, actIndex, page) => {
|
|
|
667
684
|
}
|
|
668
685
|
// Otherwise, if a current page exists:
|
|
669
686
|
else if (page) {
|
|
670
|
-
// If the
|
|
687
|
+
// If the act is a url:
|
|
671
688
|
if (act.type === 'url') {
|
|
672
689
|
// Identify the URL.
|
|
673
690
|
const resolved = act.which.replace('__dirname', __dirname);
|
|
674
691
|
requestedURL = resolved;
|
|
675
692
|
// Visit it and wait until the network is idle.
|
|
676
|
-
const {strict} = report
|
|
693
|
+
const {strict} = report;
|
|
677
694
|
let response = await goTo(report, page, requestedURL, 15000, 'networkidle', strict);
|
|
678
695
|
// If the visit fails:
|
|
679
696
|
if (response.error) {
|
|
@@ -712,23 +729,17 @@ const doActs = async (report, actIndex, page) => {
|
|
|
712
729
|
}
|
|
713
730
|
// If none of the visits succeeded:
|
|
714
731
|
if (response.error) {
|
|
715
|
-
// Report this.
|
|
716
|
-
report.jobData.aborted = true;
|
|
717
|
-
report.jobData.abortedAct = actIndex;
|
|
732
|
+
// Report this and quit.
|
|
718
733
|
addError(act, 'failure', 'ERROR: Visits failed');
|
|
719
|
-
|
|
720
|
-
actIndex = -2;
|
|
734
|
+
await abortActs();
|
|
721
735
|
}
|
|
722
736
|
// Otherwise, i.e. if the last visit attempt succeeded:
|
|
723
737
|
else {
|
|
724
738
|
// If a prohibited redirection occurred:
|
|
725
739
|
if (response.exception === 'badRedirection') {
|
|
726
|
-
// Report this.
|
|
727
|
-
report.jobData.aborted = true;
|
|
728
|
-
report.jobData.abortedAct = actIndex;
|
|
740
|
+
// Report this and quit.
|
|
729
741
|
addError(act, 'badRedirection', 'ERROR: Navigation illicitly redirected');
|
|
730
|
-
|
|
731
|
-
actIndex = -2;
|
|
742
|
+
await abortActs();
|
|
732
743
|
}
|
|
733
744
|
// Add the resulting URL to the act.
|
|
734
745
|
if (! act.result) {
|
|
@@ -744,20 +755,22 @@ const doActs = async (report, actIndex, page) => {
|
|
|
744
755
|
const result = act.result = {};
|
|
745
756
|
// If the text is to be the URL:
|
|
746
757
|
if (what === 'url') {
|
|
747
|
-
// Wait for the URL to be the exact text
|
|
758
|
+
// Wait for the URL to be the exact text.
|
|
748
759
|
try {
|
|
749
760
|
await page.waitForURL(which, {timeout: 15000});
|
|
750
761
|
result.found = true;
|
|
751
762
|
result.url = page.url();
|
|
752
763
|
}
|
|
764
|
+
// If the wait times out:
|
|
753
765
|
catch(error) {
|
|
754
|
-
|
|
766
|
+
// Quit.
|
|
767
|
+
await abortActs();
|
|
755
768
|
waitError(page, act, error, 'text in the URL');
|
|
756
769
|
}
|
|
757
770
|
}
|
|
758
771
|
// Otherwise, if the text is to be a substring of the page title:
|
|
759
772
|
else if (what === 'title') {
|
|
760
|
-
// Wait for the page title to include the text, case-insensitively
|
|
773
|
+
// Wait for the page title to include the text, case-insensitively.
|
|
761
774
|
try {
|
|
762
775
|
await page.waitForFunction(
|
|
763
776
|
text => document
|
|
@@ -772,14 +785,16 @@ const doActs = async (report, actIndex, page) => {
|
|
|
772
785
|
result.found = true;
|
|
773
786
|
result.title = await page.title();
|
|
774
787
|
}
|
|
788
|
+
// If the wait times out:
|
|
775
789
|
catch(error) {
|
|
776
|
-
|
|
790
|
+
// Quit.
|
|
791
|
+
await abortActs();
|
|
777
792
|
waitError(page, act, error, 'text in the title');
|
|
778
793
|
}
|
|
779
794
|
}
|
|
780
795
|
// Otherwise, if the text is to be a substring of the text of the page body:
|
|
781
796
|
else if (what === 'body') {
|
|
782
|
-
// Wait for the body to include the text, case-insensitively
|
|
797
|
+
// Wait for the body to include the text, case-insensitively.
|
|
783
798
|
try {
|
|
784
799
|
await page.waitForFunction(
|
|
785
800
|
text => document
|
|
@@ -793,25 +808,31 @@ const doActs = async (report, actIndex, page) => {
|
|
|
793
808
|
);
|
|
794
809
|
result.found = true;
|
|
795
810
|
}
|
|
811
|
+
// If the wait times out:
|
|
796
812
|
catch(error) {
|
|
797
|
-
|
|
813
|
+
// Quit.
|
|
814
|
+
await abortActs();
|
|
798
815
|
waitError(page, act, error, 'text in the body');
|
|
799
816
|
}
|
|
800
817
|
}
|
|
801
818
|
}
|
|
802
819
|
// Otherwise, if the act is a wait for a state:
|
|
803
820
|
else if (act.type === 'state') {
|
|
804
|
-
// Wait for it
|
|
821
|
+
// Wait for it.
|
|
805
822
|
const stateIndex = ['loaded', 'idle'].indexOf(act.which);
|
|
806
823
|
await page.waitForLoadState(
|
|
807
824
|
['domcontentloaded', 'networkidle'][stateIndex], {timeout: [10000, 15000][stateIndex]}
|
|
808
825
|
)
|
|
809
|
-
|
|
826
|
+
// If the wait times out:
|
|
827
|
+
.catch(async error => {
|
|
828
|
+
// Quit.
|
|
810
829
|
console.log(`ERROR waiting for page to be ${act.which} (${error.message})`);
|
|
811
830
|
addError(act, 'timeout', `ERROR waiting for page to be ${act.which}`);
|
|
812
|
-
|
|
831
|
+
await abortActs();
|
|
813
832
|
});
|
|
833
|
+
// If the wait succeeded:
|
|
814
834
|
if (actIndex > -2) {
|
|
835
|
+
// Add state data to the report.
|
|
815
836
|
act.result = {
|
|
816
837
|
success: true,
|
|
817
838
|
state: act.which
|
|
@@ -833,8 +854,10 @@ const doActs = async (report, actIndex, page) => {
|
|
|
833
854
|
// Otherwise, if the page has a URL:
|
|
834
855
|
else if (page.url() && page.url() !== 'about:blank') {
|
|
835
856
|
const url = page.url();
|
|
836
|
-
// If redirection is permitted or did not occur:
|
|
837
|
-
if (
|
|
857
|
+
// If redirection is inapplicable, is permitted, or did not occur:
|
|
858
|
+
if (
|
|
859
|
+
url.startsWith('file:') || ! report.strict || deSlash(url) === deSlash(requestedURL)
|
|
860
|
+
) {
|
|
838
861
|
// Add the URL to the act.
|
|
839
862
|
act.url = url;
|
|
840
863
|
// If the act is a revelation:
|
|
@@ -965,7 +988,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
965
988
|
// Initialize the arguments.
|
|
966
989
|
const args = [act.which === 'tenon' ? tenonData : page];
|
|
967
990
|
// Identify the additional validator of the test.
|
|
968
|
-
const testValidator =
|
|
991
|
+
const testValidator = actSpecs.tests[act.which];
|
|
969
992
|
// If it exists:
|
|
970
993
|
if (testValidator) {
|
|
971
994
|
// Identify its argument properties.
|
|
@@ -1085,8 +1108,9 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1085
1108
|
// If a match was found:
|
|
1086
1109
|
if (act.result.found) {
|
|
1087
1110
|
// FUNCTION DEFINITION START
|
|
1088
|
-
//
|
|
1111
|
+
// Performs a click or Enter keypress and waits for the network to be idle.
|
|
1089
1112
|
const doAndWait = async isClick => {
|
|
1113
|
+
// Perform and report the move.
|
|
1090
1114
|
const move = isClick ? 'click' : 'Enter keypress';
|
|
1091
1115
|
try {
|
|
1092
1116
|
await isClick
|
|
@@ -1095,12 +1119,14 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1095
1119
|
act.result.success = true;
|
|
1096
1120
|
act.result.move = move;
|
|
1097
1121
|
}
|
|
1122
|
+
// If the move fails:
|
|
1098
1123
|
catch(error) {
|
|
1124
|
+
// Quit and add failure data to the report.
|
|
1099
1125
|
act.result.success = false;
|
|
1100
1126
|
act.result.error = 'moveFailure';
|
|
1101
1127
|
act.result.message = `ERROR: ${move} failed`;
|
|
1102
1128
|
console.log(`ERROR: ${move} failed (${errorStart(error)})`);
|
|
1103
|
-
|
|
1129
|
+
await abortActs();
|
|
1104
1130
|
}
|
|
1105
1131
|
if (act.result.success) {
|
|
1106
1132
|
try {
|
|
@@ -1186,15 +1212,16 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1186
1212
|
}
|
|
1187
1213
|
// If the click or load failed:
|
|
1188
1214
|
catch(error) {
|
|
1189
|
-
// Quit and
|
|
1215
|
+
// Quit and add failure data to the report.
|
|
1190
1216
|
console.log(`ERROR clicking link (${errorStart(error)})`);
|
|
1191
1217
|
act.result.success = false;
|
|
1192
1218
|
act.result.error = 'unclickable';
|
|
1193
1219
|
act.result.message = 'ERROR: click or load timed out';
|
|
1194
|
-
|
|
1220
|
+
await abortActs();
|
|
1195
1221
|
}
|
|
1196
1222
|
// If the link click succeeded:
|
|
1197
1223
|
if (! act.result.error) {
|
|
1224
|
+
// Add success data to the report.
|
|
1198
1225
|
act.result.success = true;
|
|
1199
1226
|
act.result.move = 'clicked';
|
|
1200
1227
|
}
|
|
@@ -1223,7 +1250,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1223
1250
|
act.result.move = 'selected';
|
|
1224
1251
|
act.result.option = optionText;
|
|
1225
1252
|
}
|
|
1226
|
-
// Otherwise, if it is entering text
|
|
1253
|
+
// Otherwise, if it is entering text in an input element:
|
|
1227
1254
|
else if (['text', 'search'].includes(act.type)) {
|
|
1228
1255
|
act.result.attributes = {};
|
|
1229
1256
|
const {attributes} = act.result;
|
|
@@ -1263,12 +1290,12 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1263
1290
|
}
|
|
1264
1291
|
// Otherwise, i.e. if no match was found:
|
|
1265
1292
|
else {
|
|
1266
|
-
//
|
|
1293
|
+
// Quit and add failure data to the report.
|
|
1267
1294
|
act.result.success = false;
|
|
1268
1295
|
act.result.error = 'absent';
|
|
1269
1296
|
act.result.message = 'ERROR: specified element not found';
|
|
1270
1297
|
console.log('ERROR: Specified element not found');
|
|
1271
|
-
|
|
1298
|
+
await abortActs();
|
|
1272
1299
|
}
|
|
1273
1300
|
}
|
|
1274
1301
|
// Otherwise, if the act is a keypress:
|
|
@@ -1436,7 +1463,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1436
1463
|
// Otherwise, i.e. if the act type is unknown:
|
|
1437
1464
|
else {
|
|
1438
1465
|
// Add the error result to the act.
|
|
1439
|
-
addError(act, 'badType', 'ERROR: Invalid
|
|
1466
|
+
addError(act, 'badType', 'ERROR: Invalid act type');
|
|
1440
1467
|
}
|
|
1441
1468
|
}
|
|
1442
1469
|
// Otherwise, i.e. if redirection is prohibited but occurred:
|
|
@@ -1458,85 +1485,37 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1458
1485
|
}
|
|
1459
1486
|
act.endTime = Date.now();
|
|
1460
1487
|
}
|
|
1461
|
-
// Otherwise, i.e. if the
|
|
1488
|
+
// Otherwise, i.e. if the act is invalid:
|
|
1462
1489
|
else {
|
|
1463
|
-
//
|
|
1464
|
-
const errorMsg = `ERROR: Invalid
|
|
1490
|
+
// Quit and add error data to the report.
|
|
1491
|
+
const errorMsg = `ERROR: Invalid act of type ${act.type}`;
|
|
1465
1492
|
console.log(errorMsg);
|
|
1466
|
-
addError(act, '
|
|
1467
|
-
|
|
1468
|
-
actIndex = -2;
|
|
1493
|
+
addError(act, 'badAct', errorMsg);
|
|
1494
|
+
await abortActs();
|
|
1469
1495
|
}
|
|
1470
|
-
// Perform
|
|
1496
|
+
// Perform any remaining acts if not aborted.
|
|
1471
1497
|
await doActs(report, actIndex + 1, page);
|
|
1472
1498
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1499
|
+
// Otherwise, if all acts have been performed and the job succeeded:
|
|
1500
|
+
else if (! report.jobData.abortTime) {
|
|
1501
|
+
console.log('Acts completed');
|
|
1475
1502
|
await browserClose();
|
|
1476
1503
|
console.log('Browser closed');
|
|
1477
1504
|
}
|
|
1478
1505
|
};
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
const injectIndex = acts.findIndex((act, index) =>
|
|
1484
|
-
index < acts.length - 1
|
|
1485
|
-
&& act.type === 'test'
|
|
1486
|
-
&& acts[index + 1].type === 'test'
|
|
1487
|
-
&& domChangers.has(act.which)
|
|
1488
|
-
);
|
|
1489
|
-
if (injectIndex === -1) {
|
|
1490
|
-
injectMore = false;
|
|
1491
|
-
}
|
|
1492
|
-
else {
|
|
1493
|
-
const lastBrowserType = acts.reduce((browserType, act, index) => {
|
|
1494
|
-
if (act.type === 'launch' && index < injectIndex) {
|
|
1495
|
-
return act.which;
|
|
1496
|
-
}
|
|
1497
|
-
else {
|
|
1498
|
-
return browserType;
|
|
1499
|
-
}
|
|
1500
|
-
}, '');
|
|
1501
|
-
const lastURL = acts.reduce((url, act, index) => {
|
|
1502
|
-
if (act.type === 'url' && index < injectIndex) {
|
|
1503
|
-
return act.which;
|
|
1504
|
-
}
|
|
1505
|
-
else {
|
|
1506
|
-
return url;
|
|
1507
|
-
}
|
|
1508
|
-
}, '');
|
|
1509
|
-
acts.splice(
|
|
1510
|
-
injectIndex + 1,
|
|
1511
|
-
0,
|
|
1512
|
-
{
|
|
1513
|
-
type: 'launch',
|
|
1514
|
-
which: lastBrowserType,
|
|
1515
|
-
what: `${lastBrowserType} browser`
|
|
1516
|
-
},
|
|
1517
|
-
{
|
|
1518
|
-
type: 'url',
|
|
1519
|
-
which: lastURL,
|
|
1520
|
-
what: 'URL'
|
|
1521
|
-
}
|
|
1522
|
-
);
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
};
|
|
1526
|
-
// Runs a job and adds the results to the report of the job.
|
|
1506
|
+
/*
|
|
1507
|
+
Returns whether an initialized job report is valid and, if so, runs the job and adds the results
|
|
1508
|
+
to the report.
|
|
1509
|
+
*/
|
|
1527
1510
|
exports.doJob = async report => {
|
|
1528
1511
|
// If the report is valid:
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
Injection of url acts alone does not guarantee test independence.
|
|
1535
|
-
*/
|
|
1536
|
-
if (urlInject === 'yes') {
|
|
1537
|
-
injectLaunches(report.acts);
|
|
1538
|
-
}
|
|
1512
|
+
const reportInvalidity = isValidReport(report);
|
|
1513
|
+
if (reportInvalidity) {
|
|
1514
|
+
console.log(reportInvalidity);
|
|
1515
|
+
}
|
|
1516
|
+
else {
|
|
1539
1517
|
// Add initialized job data to the report.
|
|
1518
|
+
report.jobData = {};
|
|
1540
1519
|
const startTime = new Date();
|
|
1541
1520
|
report.jobData.startTime = nowString();
|
|
1542
1521
|
report.jobData.endTime = '';
|
|
@@ -1554,16 +1533,11 @@ exports.doJob = async report => {
|
|
|
1554
1533
|
report.jobData.presses = 0;
|
|
1555
1534
|
report.jobData.amountRead = 0;
|
|
1556
1535
|
report.jobData.testTimes = [];
|
|
1557
|
-
// Recursively perform the
|
|
1536
|
+
// Recursively perform the acts and get any page if debugging is on and a job was aborted.
|
|
1558
1537
|
await doActs(report, 0, null);
|
|
1559
1538
|
// Add the end time and duration to the report.
|
|
1560
1539
|
const endTime = new Date();
|
|
1561
1540
|
report.jobData.endTime = nowString();
|
|
1562
1541
|
report.jobData.elapsedSeconds = Math.floor((endTime - startTime) / 1000);
|
|
1563
|
-
return true;
|
|
1564
|
-
}
|
|
1565
|
-
else {
|
|
1566
|
-
console.log('ERROR: Initialized job report invalid');
|
|
1567
|
-
return false;
|
|
1568
1542
|
}
|
|
1569
1543
|
};
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"type": "url",
|
|
14
|
-
"which": "https://example.
|
|
14
|
+
"which": "https://example.edu",
|
|
15
15
|
"what": "Example.com",
|
|
16
16
|
"id": "example"
|
|
17
17
|
},
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"type": "url",
|
|
39
|
-
"which": "https://example.
|
|
39
|
+
"which": "https://example.edu",
|
|
40
40
|
"what": "Example.com",
|
|
41
41
|
"id": "example"
|
|
42
42
|
},
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
"batch": "",
|
|
222
222
|
"host": {
|
|
223
223
|
"id": "example",
|
|
224
|
-
"which": "https://example.
|
|
224
|
+
"which": "https://example.edu",
|
|
225
225
|
"what": "Example.com"
|
|
226
226
|
},
|
|
227
227
|
"requester": "user@domain.tld"
|