Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/puppet/test/Page.navigate.test.ts
1030 views
1
import Log from '@secret-agent/commons/Logger';
2
import IPuppetContext from '@secret-agent/interfaces/IPuppetContext';
3
import CorePlugins from '@secret-agent/core/lib/CorePlugins';
4
import { IBoundLog } from '@secret-agent/interfaces/ILog';
5
import Core from '@secret-agent/core';
6
import { TestServer } from './server';
7
import Puppet from '../index';
8
import { createTestPage, ITestPage } from './TestPage';
9
import CustomBrowserEmulator from './_CustomBrowserEmulator';
10
11
const { log } = Log(module);
12
const browserEmulatorId = CustomBrowserEmulator.id;
13
14
describe('Page.navigate', () => {
15
let server: TestServer;
16
let httpsServer: TestServer;
17
let page: ITestPage;
18
let puppet: Puppet;
19
let context: IPuppetContext;
20
const needsClosing = [];
21
const { browserEngine } = CustomBrowserEmulator.selectBrowserMeta();
22
23
beforeAll(async () => {
24
Core.use(CustomBrowserEmulator);
25
server = await TestServer.create(0);
26
httpsServer = await TestServer.createHTTPS(0);
27
puppet = new Puppet(browserEngine);
28
await puppet.start();
29
const plugins = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
30
context = await puppet.newContext(plugins, log);
31
context.on('page', event => {
32
needsClosing.push(event.page);
33
});
34
});
35
36
afterEach(async () => {
37
await page.close();
38
for (const close of needsClosing) {
39
await close.close();
40
}
41
});
42
43
beforeEach(async () => {
44
page = createTestPage(await context.newPage());
45
server.reset();
46
httpsServer.reset();
47
});
48
49
afterAll(async () => {
50
await server.stop();
51
await httpsServer.stop();
52
await context.close();
53
await puppet.close();
54
});
55
56
const options = {
57
CHROME: browserEngine.name === 'chrome',
58
WEBKIT: browserEngine.name === 'webkit',
59
FIREFOX: browserEngine.name === 'firefox',
60
};
61
62
const isWindows = process.platform === 'win32';
63
64
describe('navigate', () => {
65
it('should work with anchor navigation', async () => {
66
await page.goto(server.emptyPage);
67
expect(page.mainFrame.url).toBe(server.emptyPage);
68
await page.goto(`${server.emptyPage}#foo`);
69
expect(page.mainFrame.url).toBe(`${server.emptyPage}#foo`);
70
await page.goto(`${server.emptyPage}#bar`);
71
expect(page.mainFrame.url).toBe(`${server.emptyPage}#bar`);
72
});
73
74
it('should work with redirects', async () => {
75
server.setRedirect('/redirect/1.html', '/redirect/2.html');
76
server.setRedirect('/redirect/2.html', '/empty.html');
77
await page.goto(`${server.baseUrl}/redirect/1.html`);
78
expect(page.mainFrame.url).toBe(server.emptyPage);
79
});
80
81
it('should work with subframes return 204', async () => {
82
server.setRoute('/frames/frame.html', (req, res) => {
83
res.statusCode = 204;
84
res.end();
85
});
86
await expect(page.goto(`${server.baseUrl}/frames/one-frame.html`)).resolves.toBe(undefined);
87
});
88
89
it('should work with subframes return 204 with domcontentloaded', async () => {
90
server.setRoute('/frames/frame.html', (req, res) => {
91
res.statusCode = 204;
92
res.end();
93
});
94
const wait = page.mainFrame.waitOn(
95
'frame-lifecycle',
96
event => event.name === 'DOMContentLoaded',
97
);
98
await page.goto(`${server.baseUrl}/frames/one-frame.html`);
99
await expect(wait).resolves.toBeTruthy();
100
});
101
102
it('should fail when server returns 204', async () => {
103
// Webkit just loads an empty page.
104
server.setRoute('/empty.html', (req, res) => {
105
res.statusCode = 204;
106
res.end();
107
});
108
let error = null;
109
await page.goto(server.emptyPage).catch(e => (error = e));
110
expect(error).not.toBe(null);
111
if (browserEngine.name === 'chrome') expect(error.message).toContain('net::ERR_ABORTED');
112
else if (browserEngine.name === 'webkit')
113
expect(error.message).toContain('Aborted: 204 No Content');
114
else expect(error.message).toContain('NS_BINDING_ABORTED');
115
});
116
117
it('should work when page calls history API in beforeunload', async () => {
118
await page.goto(server.emptyPage);
119
await page.evaluate(`(() => {
120
window.addEventListener(
121
'beforeunload',
122
() => history.replaceState(null, 'initial', window.location.href),
123
false,
124
);
125
})()`);
126
const waitForLoad = page.mainFrame.waitOn(
127
'frame-lifecycle',
128
event => event.name === 'DOMContentLoaded',
129
);
130
await page.navigate(`${server.baseUrl}/grid.html`);
131
await expect(waitForLoad).resolves.toBeTruthy();
132
});
133
134
it('should fail when navigating to bad url', async () => {
135
let error = null;
136
await page.goto('asdfasdf').catch(e => (error = e));
137
if (options.CHROME || options.WEBKIT)
138
expect(error.message).toContain('Cannot navigate to invalid URL');
139
else expect(error.message).toContain('Invalid url');
140
});
141
142
it('should fail when main resources failed to load', async () => {
143
let error = null;
144
await page.goto('http://localhost:44123/non-existing-url').catch(e => (error = e));
145
if (options.CHROME) expect(error.message).toContain('net::ERR_CONNECTION_REFUSED');
146
else if (options.WEBKIT && isWindows)
147
expect(error.message).toContain(`Couldn't connect to server`);
148
else if (options.WEBKIT) expect(error.message).toContain('Could not connect');
149
else expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED');
150
});
151
152
it('should fail when replaced by another navigation', async () => {
153
let anotherPromise;
154
server.setRoute('/empty.html', () => {
155
anotherPromise = page.goto(`${server.baseUrl}/one-style.html`);
156
// Hang request to empty.html.
157
});
158
const error = await page.goto(`${server.baseUrl}/empty.html`).catch(e => e);
159
await anotherPromise;
160
if (options.CHROME) expect(error.message).toContain('net::ERR_ABORTED');
161
else if (options.WEBKIT) expect(error.message).toContain('cancelled');
162
else expect(error.message).toContain('NS_BINDING_ABORTED');
163
});
164
165
it('should work when navigating to data url', async () => {
166
await page.goto('data:text/html,hello');
167
expect(page.mainFrame.url).toBe('data:text/html,hello');
168
});
169
170
it('should set last url in redirect chain', async () => {
171
server.setRedirect('/redirect/1.html', '/redirect/2.html');
172
server.setRedirect('/redirect/2.html', '/redirect/3.html');
173
server.setRedirect('/redirect/3.html', server.emptyPage);
174
await page.goto(`${server.baseUrl}/redirect/1.html`);
175
expect(page.mainFrame.url).toBe(server.emptyPage);
176
});
177
178
it('should not leak listeners during navigation of 20 pages', async () => {
179
let warning = null;
180
const warningHandler = w => (warning = w);
181
process.on('warning', warningHandler);
182
await Promise.all(
183
[...Array(20)].map(async () => {
184
const testPage = await context.newPage();
185
await testPage.navigate(server.emptyPage);
186
await testPage.close();
187
}),
188
);
189
process.off('warning', warningHandler);
190
expect(warning).toBe(null);
191
}, 30e3);
192
193
it('should not leak listeners during 20 frameNavigated', async () => {
194
let warning = null;
195
const warningHandler = w => (warning = w);
196
process.on('warning', warningHandler);
197
const promises = [...Array(20)].map(() => page.mainFrame.waitOn('frame-navigated'));
198
await page.goto(server.emptyPage);
199
await Promise.all(promises);
200
process.off('warning', warningHandler);
201
expect(warning).toBe(null);
202
});
203
204
it('should work with self requesting page', async () => {
205
await expect(page.goto(`${server.baseUrl}/self-request.html`)).resolves.toBe(undefined);
206
});
207
208
it('should be able to navigate to a page controlled by service worker', async () => {
209
await page.goto(`${server.baseUrl}/serviceworkers/fetch/sw.html`);
210
await page.evaluate(`window.activationPromise`);
211
await expect(page.goto(`${server.baseUrl}/serviceworkers/fetch/sw.html`)).resolves.toBe(
212
undefined,
213
);
214
});
215
216
it('should fail when canceled by another navigation', async () => {
217
server.setRoute('/one-style.html', () => {});
218
const failed = page.goto(`${server.baseUrl}/one-style.html`).catch(e => e);
219
await server.waitForRequest('/one-style.html');
220
await page.goto(`${server.baseUrl}/empty.html`);
221
const error = await failed;
222
expect(error.message).toBeTruthy();
223
});
224
225
it('should work with lazy loading iframes', async () => {
226
await page.goto(`${server.baseUrl}/frames/lazy-frame.html`);
227
expect(page.frames.length).toBe(2);
228
});
229
230
it('should work with mixed content', async () => {
231
httpsServer.setRoute('/mixedcontent.html', (req, res) => {
232
res.end(`<iframe src=${server.emptyPage}></iframe>`);
233
});
234
await expect(page.goto(`${httpsServer.baseUrl}/mixedcontent.html`, 'load')).resolves.toBe(
235
undefined,
236
);
237
expect(page.frames).toHaveLength(2);
238
});
239
});
240
241
describe('history', () => {
242
it('page.goBack should work', async () => {
243
expect(await page.goBack()).toBe(null);
244
245
await page.goto(server.emptyPage);
246
await page.goto(server.url('grid.html'));
247
248
await page.goBack();
249
expect(page.mainFrame.url).toContain(server.emptyPage);
250
251
await page.goForward();
252
expect(page.mainFrame.url).toContain('/grid.html');
253
});
254
255
it('page.goBack should work with HistoryAPI', async () => {
256
await page.goto(server.emptyPage);
257
await page.evaluate(`
258
history.pushState({}, '', '/first.html');
259
history.pushState({}, '', '/second.html');
260
`);
261
expect(page.mainFrame.url).toBe(server.url('second.html'));
262
263
await page.goBack();
264
expect(page.mainFrame.url).toBe(server.url('first.html'));
265
await page.goBack();
266
expect(page.mainFrame.url).toBe(server.emptyPage);
267
await page.goForward();
268
expect(page.mainFrame.url).toBe(server.url('first.html'));
269
});
270
});
271
});
272
273