Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/full-client/test/detach.test.ts
1028 views
1
import { Helpers } from '@secret-agent/testing';
2
import { ITestKoaServer } from '@secret-agent/testing/helpers';
3
import { IState } from '@secret-agent/client/lib/Agent';
4
import Resolvable from '@secret-agent/commons/Resolvable';
5
import StateMachine from 'awaited-dom/base/StateMachine';
6
import Core from '@secret-agent/core';
7
import IAwaitedOptions from '@secret-agent/client/interfaces/IAwaitedOptions';
8
import FrameEnvironment from '@secret-agent/core/lib/FrameEnvironment';
9
import { Agent, Handler, RemoteConnectionToCore } from '../index';
10
11
let koaServer: ITestKoaServer;
12
let handler: Handler;
13
beforeAll(async () => {
14
handler = new Handler();
15
Helpers.onClose(() => handler.close(), true);
16
koaServer = await Helpers.runKoaServer();
17
});
18
afterAll(Helpers.afterAll);
19
afterEach(Helpers.afterEach);
20
21
describe('basic Detach tests', () => {
22
it('can detach a document', async () => {
23
koaServer.get('/detach-1', ctx => {
24
ctx.body = `
25
<body>
26
<a href="#page1">Click Me</a>
27
</body>
28
`;
29
});
30
const agent = await openBrowser(`/detach-1`);
31
const links = await agent.document.querySelectorAll('a').length;
32
expect(links).toBe(1);
33
34
const frozenTab = await agent.detach(agent.activeTab);
35
const detachedTab = await frozenTab.document.querySelectorAll('a').length;
36
expect(detachedTab).toBe(1);
37
});
38
39
it('should keep the original tab detached', async () => {
40
koaServer.get('/detach-grow', ctx => {
41
ctx.body = `
42
<body>
43
<a href="javascript:void(0);" onclick="clicker()">Click Me</a>
44
45
<script>
46
function clicker() {
47
const elem = document.createElement('A');
48
document.querySelector('a').after(elem)
49
}
50
</script>
51
</body>
52
`;
53
});
54
const agent = await openBrowser(`/detach-grow`);
55
const links = await agent.document.querySelectorAll('a').length;
56
expect(links).toBe(1);
57
58
const frozenTab = await agent.detach(agent.activeTab);
59
const detachedTab = await frozenTab.document.querySelectorAll('a').length;
60
expect(detachedTab).toBe(1);
61
62
await agent.click(agent.document.querySelector('a'));
63
const linksAfterClick = await agent.document.querySelectorAll('a').length;
64
expect(linksAfterClick).toBe(2);
65
66
const detachedLinksAfterClick = await frozenTab.document.querySelectorAll('a').length;
67
expect(detachedLinksAfterClick).toBe(1);
68
69
const frozenTab2 = await agent.detach(agent.activeTab);
70
const detachedTab2 = await frozenTab2.document.querySelectorAll('a').length;
71
expect(detachedTab2).toBe(2);
72
});
73
74
it('can handle jsPaths when element not present', async () => {
75
let run = 0;
76
koaServer.get('/detach-notthere', ctx => {
77
run += 1;
78
if (run === 1) {
79
ctx.body = `
80
<body>
81
<a id="link1">Click Me</a>
82
<div id="loop">
83
<div class="parent">
84
<div class="child">1</div>
85
<div class="child">2</div>
86
<div class="child">3</div>
87
</div>
88
</div>
89
</body>
90
`;
91
} else {
92
ctx.body = `
93
<body>
94
<a id="link2">Click Me</a>
95
</body>
96
`;
97
}
98
});
99
100
{
101
const agent = await openBrowser(`/detach-notthere`);
102
await mockDetach(agent);
103
const frozenTab = await agent.detach(agent.activeTab);
104
const link = await frozenTab.document.querySelector('#link1');
105
await expect(link.innerText).resolves.toBe('Click Me');
106
await expect(
107
frozenTab.document.querySelector('#loop').firstElementChild.innerHTML,
108
).resolves.toBeTruthy();
109
const parent = await frozenTab.document.querySelectorAll('.child');
110
for (const child of parent) {
111
await expect(child.hasAttribute('class')).resolves.toBe(true);
112
}
113
await agent.close();
114
}
115
{
116
const agent = await openBrowser(`/detach-notthere`);
117
await mockDetach(agent);
118
const frozenTab = await agent.detach(agent.activeTab);
119
const link = await frozenTab.document.querySelector('#link1');
120
expect(link).toBe(null);
121
122
await expect(
123
frozenTab.document.querySelector('#loop').firstElementChild.innerHTML,
124
).rejects.toThrow();
125
const parent = await frozenTab.document.querySelectorAll('.child');
126
for (const child of parent) {
127
expect(child).not.toBeTruthy();
128
}
129
await agent.close();
130
}
131
});
132
133
it('will wait for flushes to complete', async () => {
134
koaServer.get('/detach-flush', ctx => {
135
ctx.body = `
136
<body>
137
<a id="link1" href="#page1">Click Me</a>
138
</body>
139
`;
140
});
141
{
142
// @ts-ignore
143
const connection = handler.connections[0];
144
const sendRequestSpy = jest.spyOn(connection, 'sendRequest');
145
const agent = await openBrowser(`/detach-flush`);
146
await mockDetach(agent, 'detach-flush');
147
const frozenTab = await agent.detach(agent.activeTab);
148
const link = await frozenTab.document.querySelector('#link1');
149
await link.getAttribute('id');
150
await link.getAttribute('class');
151
await link.dataset;
152
const links = await frozenTab.document.querySelectorAll('a').length;
153
expect(links).toBe(1);
154
155
const outgoingCommands = sendRequestSpy.mock.calls;
156
expect(outgoingCommands.map(c => c[0].command)).toMatchObject([
157
'Session.create',
158
'Session.addEventListener',
159
'Tab.goto',
160
'FrameEnvironment.waitForLoad',
161
'Session.detachTab',
162
'FrameEnvironment.execJsPath',
163
'FrameEnvironment.execJsPath',
164
'FrameEnvironment.execJsPath',
165
'FrameEnvironment.execJsPath',
166
'FrameEnvironment.execJsPath',
167
]);
168
await agent.close();
169
}
170
{
171
await Core.start();
172
Helpers.onClose(() => Core.shutdown());
173
174
const connection = new RemoteConnectionToCore({ host: await Core.server.address });
175
await connection.connect();
176
const sendRequest = connection.sendRequest.bind(connection);
177
178
const flushPromise = new Resolvable<void>();
179
const sendRequestSpy = jest.spyOn(connection, 'sendRequest');
180
sendRequestSpy.mockImplementation(async request => {
181
if (request.command === 'Session.flush' && !flushPromise.isResolved) {
182
flushPromise.resolve();
183
await new Promise(setImmediate);
184
}
185
return sendRequest(request);
186
});
187
188
const agent = await new Agent({ connectionToCore: connection });
189
Helpers.needsClosing.push(agent);
190
191
const connectionToClient = Core.connections[Core.connections.length - 1];
192
// @ts-ignore
193
const recordCommands = connectionToClient.recordCommands.bind(connectionToClient);
194
const recordCommandsSpy = jest.spyOn<any, any>(connectionToClient, 'recordCommands');
195
const waitForClose = new Resolvable<void>();
196
recordCommandsSpy.mockImplementation(async (...args) => {
197
if (!waitForClose.isResolved && (args[2] as any).length > 10) {
198
await waitForClose.promise;
199
}
200
201
return recordCommands(...args);
202
});
203
204
await agent.goto(`${koaServer.baseUrl}/detach-flush`);
205
await agent.waitForPaintingStable();
206
await mockDetach(agent, 'detach-flush');
207
const frozenTab = await agent.detach(agent.activeTab);
208
const link = await frozenTab.document.querySelector('#link1');
209
await link.getAttribute('id');
210
await link.getAttribute('class');
211
await link.dataset;
212
213
const frameSpy = jest.spyOn(FrameEnvironment.prototype, 'recordDetachedJsPath');
214
215
const frameState = StateMachine<any, IAwaitedOptions>();
216
const coreFrame = await frameState.getState(frozenTab.mainFrameEnvironment).coreFrame;
217
for (let i = 0; i < 1001; i += 1) {
218
coreFrame.recordDetachedJsPath(1, new Date(), new Date());
219
}
220
await flushPromise;
221
const links = await frozenTab.document.querySelectorAll('a').length;
222
expect(links).toBe(1);
223
224
await Promise.all([agent.close(), waitForClose.resolve()]);
225
const outgoingCommands = sendRequestSpy.mock.calls;
226
expect(outgoingCommands.map(c => c[0].command)).toMatchObject([
227
'Session.create',
228
'Tab.goto',
229
'FrameEnvironment.waitForLoad',
230
'Session.detachTab',
231
'Session.flush',
232
'Session.flush',
233
'Session.close',
234
]);
235
236
expect(frameSpy).toHaveBeenCalledTimes(1006);
237
}
238
});
239
});
240
241
async function openBrowser(path: string) {
242
const agent = await handler.createAgent();
243
Helpers.needsClosing.push(agent);
244
await agent.goto(`${koaServer.baseUrl}${path}`);
245
await agent.waitForPaintingStable();
246
return agent;
247
}
248
249
const { getState } = StateMachine<any, IState>();
250
async function mockDetach(agent: Partial<Agent>, callsitePath = 'path1') {
251
const coreSession = await getState(agent).connection.getCoreSessionOrReject();
252
const origDetach = coreSession.detachTab;
253
254
const interceptDetach = jest.spyOn(coreSession, 'detachTab');
255
interceptDetach.mockImplementationOnce((tab, callSitePath: string, key?: string) => {
256
return origDetach.call(coreSession, tab, callsitePath, key);
257
});
258
}
259
260