/
user-flow-modified.js
246 lines (237 loc) · 8.3 KB
/
user-flow-modified.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/**
* This is a modified version of the user-flow-original.js script, which
* is the raw user flow exported directly from the Chrome DevTools Recorder Panel.
* The code is commented out with each one of the modifications I've made (numbered 1 to 8).
*/
const puppeteer = require('puppeteer');
// 1. Add dependencies
const open = require('open');
const fs = require('fs');
const lighthouse = require('lighthouse/lighthouse-core/fraggle-rock/api.js');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 2. Create a new flow
const flow = await lighthouse.startFlow(page, { name: 'My User Flow' });
async function waitForSelectors(selectors, frame) {
for (const selector of selectors) {
try {
return await waitForSelector(selector, frame);
} catch (err) {
console.error(err);
}
}
throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors));
}
async function waitForSelector(selector, frame) {
if (selector instanceof Array) {
let element = null;
for (const part of selector) {
if (!element) {
element = await frame.waitForSelector(part);
} else {
element = await element.$(part);
}
if (!element) {
throw new Error('Could not find element: ' + part);
}
element = (await element.evaluateHandle(el => el.shadowRoot ? el.shadowRoot : el)).asElement();
}
if (!element) {
throw new Error('Could not find element: ' + selector.join('|'));
}
return element;
}
const element = await frame.waitForSelector(selector);
if (!element) {
throw new Error('Could not find element: ' + selector);
}
return element;
}
async function waitForElement(step, frame) {
const count = step.count || 1;
const operator = step.operator || '>=';
const comp = {
'==': (a, b) => a === b,
'>=': (a, b) => a >= b,
'<=': (a, b) => a <= b,
};
const compFn = comp[operator];
await waitForFunction(async () => {
const elements = await querySelectorsAll(step.selectors, frame);
return compFn(elements.length, count);
});
}
async function querySelectorsAll(selectors, frame) {
for (const selector of selectors) {
const result = await querySelectorAll(selector, frame);
if (result.length) {
return result;
}
}
return [];
}
async function querySelectorAll(selector, frame) {
if (selector instanceof Array) {
let elements = [];
let i = 0;
for (const part of selector) {
if (i === 0) {
elements = await frame.$$(part);
} else {
const tmpElements = elements;
elements = [];
for (const el of tmpElements) {
elements.push(...(await el.$$(part)));
}
}
if (elements.length === 0) {
return [];
}
const tmpElements = [];
for (const el of elements) {
const newEl = (await el.evaluateHandle(el => el.shadowRoot ? el.shadowRoot : el)).asElement();
if (newEl) {
tmpElements.push(newEl);
}
}
elements = tmpElements;
i++;
}
return elements;
}
const element = await frame.$$(selector);
if (!element) {
throw new Error('Could not find element: ' + selector);
}
return element;
}
async function waitForFunction(fn) {
let isActive = true;
setTimeout(() => {
isActive = false;
}, 5000);
while (isActive) {
const result = await fn();
if (result) {
return;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
throw new Error('Timed out');
}
{
const targetPage = page;
await targetPage.setViewport({"width":951,"height":894})
}
{
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
await targetPage.goto('https://coffee-cart.netlify.app/');
await Promise.all(promises);
}
// 3. Capture a cold navigation report
{
const targetPage = page;
await flow.navigate('https://coffee-cart.netlify.app/', {
stepName: 'Cold navigation'
});
}
// 4. Capture a warm navigation report
{
const targetPage = page;
await flow.navigate('https://coffee-cart.netlify.app/', {
stepName: 'Warm navigation',
configContext: {
settingsOverrides: { disableStorageReset: true },
}
});
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Cappucino"],["#app > div:nth-child(3) > ul > li:nth-child(3) > div > div > div.cup-body"]], targetPage);
await element.click({ offset: { x: 178.90185546875, y: 166.6608657836914} });
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Americano"],["#app > div:nth-child(3) > ul > li:nth-child(6) > div > div > div.cup-body"]], targetPage);
await element.click({ offset: { x: 140.90185546875, y: 75.28585815429688} });
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Cart page"],["#app > ul > li:nth-child(2) > a"]], targetPage);
await element.click({ offset: { x: 37.6875, y: 16} });
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Proceed to checkout"],["#app > div.list > div > button"]], targetPage);
await element.click({ offset: { x: 162.5, y: 23.921875} });
}
// 5. Capture a snapshot report
{
await flow.snapshot({ stepName: 'Checkout modal opened' });
}
// 6. Start capturing a timespan report
{
await flow.startTimespan({ stepName: 'Checkout flow' });
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Name"],["#name"]], targetPage);
await element.click({ offset: { x: 54.515625, y: 4.921875} });
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Name"],["#name"]], targetPage);
const type = await element.evaluate(el => el.type);
if (["textarea","select-one","text","url","tel","search","password","number","email"].includes(type)) {
await element.type('Maxi');
} else {
await element.focus();
await element.evaluate((el, value) => {
el.value = value;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
}, "Maxi");
}
}
{
const targetPage = page;
await targetPage.keyboard.down("Tab");
}
{
const targetPage = page;
await targetPage.keyboard.up("Tab");
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Email"],["#email"]], targetPage);
const type = await element.evaluate(el => el.type);
if (["textarea","select-one","text","url","tel","search","password","number","email"].includes(type)) {
await element.type('charca@gmail.com');
} else {
await element.focus();
await element.evaluate((el, value) => {
el.value = value;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
}, "charca@gmail.com");
}
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Submit"],["#app > div.list > div > div > div > form > div:nth-child(4) > button"]], targetPage);
await element.click({ offset: { x: 79.859375, y: 28.859375} });
}
// 7. End the timespan report
{
await flow.endTimespan();
}
await browser.close();
// 8. Generate the report, write the output to an HTML file, and open the file in a browser
const reportPath = __dirname + '/user-flow.report.html';
const report = flow.generateReport();
fs.writeFileSync(reportPath, report);
open(reportPath, { wait: false });
})();