hof 23.0.2 → 23.0.4-session-timeout-beta
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/CHANGELOG.md +13 -0
- package/frontend/govuk-template/govuk_template_generated.html +118 -0
- package/frontend/themes/gov-uk/client-js/session-timeout-dialog.js +8 -3
- package/index.js +2 -1
- package/package.json +2 -2
- package/sandbox/apps/sandbox/translations/en/default.json +278 -0
- package/sandbox/public/assets/index-CsI1K_CH.js +60 -0
- package/sandbox/public/css/app.css +11714 -0
- package/sandbox/public/css/app.css.map +1 -0
- package/sandbox/public/images/govuk-logo.svg +25 -0
- package/sandbox/public/images/icons/icon-caret-left.png +0 -0
- package/sandbox/public/images/icons/icon-complete.png +0 -0
- package/sandbox/public/images/icons/icon-cross-remove-sign.png +0 -0
- package/sandbox/public/js/bundle.js +27 -0
- package/sandbox/public/js/bundle.js.map +1 -0
- package/utilities/autofill/index.js +169 -145
|
@@ -3,187 +3,211 @@
|
|
|
3
3
|
|
|
4
4
|
const url = require('url');
|
|
5
5
|
const Inputs = require('./inputs');
|
|
6
|
-
const Promise = require('bluebird');
|
|
7
6
|
|
|
8
7
|
const debug = require('debug')('hof:util:autofill');
|
|
9
8
|
|
|
10
9
|
const MAX_LOOPS = 3;
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
function getRemoteFilePath(uploadResult) {
|
|
12
|
+
if (uploadResult && typeof uploadResult === 'object' && uploadResult.value) {
|
|
13
|
+
return uploadResult.value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return uploadResult;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = browser => async (target, input, opts) => {
|
|
13
20
|
const options = opts || {};
|
|
14
21
|
options.maxLoops = options.maxLoops || MAX_LOOPS;
|
|
15
|
-
|
|
16
22
|
const getValue = Inputs(input);
|
|
17
|
-
|
|
18
23
|
let last;
|
|
19
24
|
let count = 0;
|
|
20
25
|
|
|
21
|
-
function completeTextField(element, name) {
|
|
26
|
+
async function completeTextField(element, name) {
|
|
22
27
|
const value = getValue(name, 'text');
|
|
23
28
|
debug(`Filling field: ${name} with value: ${value}`);
|
|
24
|
-
|
|
25
|
-
.
|
|
26
|
-
.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
try {
|
|
30
|
+
await element.clearValue();
|
|
31
|
+
await element.setValue(value);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
// any error here is *probably* because the field is hidden
|
|
34
|
+
// ignore and hope for the best
|
|
35
|
+
}
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
function completeFileField(element, name) {
|
|
38
|
+
async function completeFileField(element, name) {
|
|
34
39
|
const value = getValue(name, 'file');
|
|
35
40
|
if (value) {
|
|
36
41
|
debug(`Uploading file: ${value}`);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
42
|
+
const remotePath = getRemoteFilePath(await browser.uploadFile(value));
|
|
43
|
+
debug(`Uploaded file: ${value} - remote path ${remotePath}`);
|
|
44
|
+
await element.setValue(remotePath);
|
|
45
|
+
} else {
|
|
46
|
+
debug(`No file specified for input ${name} - ignoring`);
|
|
43
47
|
}
|
|
44
|
-
debug(`No file specified for input ${name} - ignoring`);
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
function
|
|
50
|
+
async function completeRadioGroup(name) {
|
|
48
51
|
const value = getValue(name, 'radio');
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return browser.elementIdClick(radios.value[index].ELEMENT);
|
|
55
|
-
});
|
|
52
|
+
const radios = await browser.$$(`input[type="radio"][name="${name}"]`);
|
|
53
|
+
|
|
54
|
+
if (!radios.length) {
|
|
55
|
+
debug(`No radio inputs found for ${name}`);
|
|
56
|
+
return;
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
|
|
59
|
+
if (!value) {
|
|
60
|
+
debug(`Checking random radio: ${name}`);
|
|
61
|
+
const index = Math.floor(Math.random() * radios.length);
|
|
62
|
+
if (!await radios[index].isSelected()) {
|
|
63
|
+
await radios[index].click();
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
for (const radio of radios) {
|
|
67
|
+
const val = await radio.getAttribute('value');
|
|
68
|
+
if (val === value) {
|
|
69
|
+
debug(`Checking radio: ${name} with value: ${val}`);
|
|
70
|
+
if (!await radio.isSelected()) {
|
|
71
|
+
await radio.click();
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
62
74
|
}
|
|
63
|
-
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
debug(`Ignoring radio group: ${name} - no option matches ${value}`);
|
|
78
|
+
}
|
|
64
79
|
}
|
|
65
80
|
|
|
66
|
-
function completeCheckbox(element, name) {
|
|
81
|
+
async function completeCheckbox(element, name) {
|
|
67
82
|
const value = getValue(name, 'checkbox');
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
debug(`Ignoring checkbox: ${name} with value: ${val.value} - looking for ${value}`);
|
|
90
|
-
}));
|
|
83
|
+
const val = await element.getAttribute('value');
|
|
84
|
+
const checked = await element.isSelected();
|
|
85
|
+
if (value === null) {
|
|
86
|
+
if (!checked) {
|
|
87
|
+
debug(`Leaving checkbox: ${name} blank`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
debug(`Unchecking checkbox: ${name}`);
|
|
91
|
+
await element.click();
|
|
92
|
+
} else if (!value && !checked) {
|
|
93
|
+
debug(`Checking checkbox: ${name} with value: ${val}`);
|
|
94
|
+
await element.click();
|
|
95
|
+
} else if (value && value.indexOf(val) > -1 && !checked) {
|
|
96
|
+
debug(`Checking checkbox: ${name} with value: ${val}`);
|
|
97
|
+
await element.click();
|
|
98
|
+
} else if (value && value.indexOf(val) === -1 && checked) {
|
|
99
|
+
debug(`Unchecking checkbox: ${name} with value: ${val}`);
|
|
100
|
+
await element.click();
|
|
101
|
+
} else {
|
|
102
|
+
debug(`Ignoring checkbox: ${name} with value: ${val} - looking for ${value}`);
|
|
103
|
+
}
|
|
91
104
|
}
|
|
92
105
|
|
|
93
|
-
function completeSelectElement(element, name) {
|
|
106
|
+
async function completeSelectElement(element, name) {
|
|
94
107
|
const value = getValue(name, 'select');
|
|
95
108
|
if (!value) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
const selectOptions = await element.$$('option');
|
|
110
|
+
if (selectOptions.length > 1) {
|
|
111
|
+
const index = 1 + Math.floor(Math.random() * (selectOptions.length - 1));
|
|
112
|
+
debug(`Selecting option: ${index} from select box: ${name}`);
|
|
113
|
+
await element.selectByIndex(index);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
debug(`Selecting options: ${value} from select box: ${name}`);
|
|
117
|
+
await element.selectByAttribute('value', value);
|
|
102
118
|
}
|
|
103
|
-
debug(`Selecting options: ${value} from select box: ${name}`);
|
|
104
|
-
return browser.selectByValue(`select[name="${name}"]`, value);
|
|
105
119
|
}
|
|
106
120
|
|
|
107
|
-
function completeStep(path) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return completeFileField(field.ELEMENT, name.value);
|
|
121
|
-
} else if (type.value === 'text') {
|
|
122
|
-
return completeTextField(field.ELEMENT, name.value);
|
|
123
|
-
}
|
|
124
|
-
debug(`Ignoring field of type ${type.value}`);
|
|
125
|
-
})), {concurrency: 1});
|
|
126
|
-
})
|
|
127
|
-
.elements('select')
|
|
128
|
-
.then(fields => {
|
|
129
|
-
debug(`Found ${fields.value.length} <select> elements`);
|
|
130
|
-
return Promise.map(fields.value, field => browser.elementIdAttribute(field.ELEMENT, 'name')
|
|
131
|
-
.then(name => completeSelectElement(field.ELEMENT, name.value)));
|
|
132
|
-
})
|
|
133
|
-
.elements('textarea')
|
|
134
|
-
.then(fields => {
|
|
135
|
-
debug(`Found ${fields.value.length} <textarea> elements`);
|
|
136
|
-
return Promise.map(fields.value, field => browser.elementIdAttribute(field.ELEMENT, 'name')
|
|
137
|
-
.then(name => completeTextField(field.ELEMENT, name.value)));
|
|
138
|
-
})
|
|
139
|
-
.then(() => {
|
|
140
|
-
if (options.screenshots) {
|
|
141
|
-
const screenshot = require('path').resolve(options.screenshots, 'hof-autofill.pre-submit.png');
|
|
142
|
-
return browser.saveScreenshot(screenshot);
|
|
121
|
+
async function completeStep(path) {
|
|
122
|
+
const completedRadioGroups = new Set();
|
|
123
|
+
|
|
124
|
+
// Fill inputs
|
|
125
|
+
const inputs = await browser.$$('input');
|
|
126
|
+
debug(`Found ${inputs.length} <input> elements`);
|
|
127
|
+
for (const element of inputs) {
|
|
128
|
+
const type = await element.getAttribute('type');
|
|
129
|
+
const name = await element.getAttribute('name');
|
|
130
|
+
if (type === 'radio') {
|
|
131
|
+
if (!completedRadioGroups.has(name)) {
|
|
132
|
+
completedRadioGroups.add(name);
|
|
133
|
+
await completeRadioGroup(name);
|
|
143
134
|
}
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
135
|
+
} else if (type === 'checkbox') {
|
|
136
|
+
await completeCheckbox(element, name);
|
|
137
|
+
} else if (type === 'file') {
|
|
138
|
+
await completeFileField(element, name);
|
|
139
|
+
} else if (type === 'text') {
|
|
140
|
+
await completeTextField(element, name);
|
|
141
|
+
} else {
|
|
142
|
+
debug(`Ignoring field of type ${type}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Fill selects
|
|
147
|
+
const selects = await browser.$$('select');
|
|
148
|
+
debug(`Found ${selects.length} <select> elements`);
|
|
149
|
+
for (const element of selects) {
|
|
150
|
+
const name = await element.getAttribute('name');
|
|
151
|
+
await completeSelectElement(element, name);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Fill textareas
|
|
155
|
+
const textareas = await browser.$$('textarea');
|
|
156
|
+
debug(`Found ${textareas.length} <textarea> elements`);
|
|
157
|
+
for (const element of textareas) {
|
|
158
|
+
const name = await element.getAttribute('name');
|
|
159
|
+
await completeTextField(element, name);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (options.screenshots) {
|
|
163
|
+
const screenshot = path.resolve(options.screenshots, 'hof-autofill.pre-submit.png');
|
|
164
|
+
await browser.saveScreenshot(screenshot);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
debug('Submitting form');
|
|
168
|
+
const submitBtn = await browser.$('input[type="submit"], button[type="submit"]');
|
|
169
|
+
if (!await submitBtn.isExisting()) {
|
|
170
|
+
throw new Error('No submit control found on page');
|
|
171
|
+
}
|
|
172
|
+
await submitBtn.click();
|
|
173
|
+
|
|
174
|
+
const p = await browser.getUrl();
|
|
175
|
+
const u = url.parse(p);
|
|
176
|
+
debug(`New page is: ${u.path}`);
|
|
177
|
+
if (u.path !== path) {
|
|
178
|
+
debug(`Checking current path ${u.path} against last path ${last}`);
|
|
179
|
+
if (last === u.path) {
|
|
180
|
+
count++;
|
|
181
|
+
debug(`Stuck on path ${u.path} for ${count} iterations`);
|
|
182
|
+
if (count === options.maxLoops) {
|
|
183
|
+
if (options.screenshots) {
|
|
184
|
+
const screenshot = path.resolve(options.screenshots, 'hof-autofill.debug.png');
|
|
185
|
+
await browser.saveScreenshot(screenshot);
|
|
186
|
+
throw new Error(`Progress stuck at ${u.path} - screenshot saved to ${screenshot}`);
|
|
173
187
|
}
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
.then(() => {
|
|
184
|
-
throw e;
|
|
185
|
-
}));
|
|
188
|
+
throw new Error(`Progress stuck at ${u.path}`);
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
count = 0;
|
|
192
|
+
}
|
|
193
|
+
last = u.path;
|
|
194
|
+
return completeStep(path);
|
|
195
|
+
}
|
|
196
|
+
debug(`Arrived at ${path}. Done.`);
|
|
186
197
|
}
|
|
187
198
|
|
|
188
|
-
|
|
199
|
+
try {
|
|
200
|
+
await completeStep(target);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
try {
|
|
203
|
+
const content = await browser.$('#content');
|
|
204
|
+
const text = await content.getText();
|
|
205
|
+
debug('PAGE CONTENT >>>>>>');
|
|
206
|
+
debug(text);
|
|
207
|
+
debug('END PAGE CONTENT >>>>>>');
|
|
208
|
+
} catch (err) {
|
|
209
|
+
// ignore error
|
|
210
|
+
}
|
|
211
|
+
throw e;
|
|
212
|
+
}
|
|
189
213
|
};
|