Skip to content

Commit 44a6b12

Browse files
committed
feat(client): expose frames
1 parent 21ca261 commit 44a6b12

File tree

82 files changed

+2596
-1419
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+2596
-1419
lines changed

.github/workflows/lint-and-test.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,19 @@ jobs:
130130
env:
131131
SA_SHOW_REPLAY: false
132132
NODE_ENV: test
133+
SA_SESSIONS_DIR: .sessions
134+
135+
- name: 'Tar files'
136+
if: ${{ failure() }}
137+
run: tar -cvf test-dbs.tar ./build/.sessions
138+
139+
- name: Upload Databases
140+
if: ${{ failure() }}
141+
uses: actions/upload-artifact@v2
142+
with:
143+
name: test-dbs-${{matrix.os}}-${{ matrix.node-version }}
144+
path: test-dbs.tar
145+
retention-days: 1
133146

134147
- name: Coverage
135148
run: npm -g install codecov && codecov

client/connections/ConnectionToCore.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export default abstract class ConnectionToCore extends TypedEventEmitter<{
9292
if (this.connectPromise.isResolved) return;
9393

9494
const connectResult = await this.internalSendRequestAndWait({
95-
command: 'connect',
95+
command: 'Core.connect',
9696
args: [this.connectOptions],
9797
});
9898
if (connectResult?.data) {
@@ -127,7 +127,7 @@ export default abstract class ConnectionToCore extends TypedEventEmitter<{
127127
try {
128128
await this.internalSendRequestAndWait(
129129
{
130-
command: 'disconnect',
130+
command: 'Core.disconnect',
131131
args: [fatalError],
132132
},
133133
2e3,
@@ -182,7 +182,7 @@ export default abstract class ConnectionToCore extends TypedEventEmitter<{
182182
}
183183

184184
public async createSession(options: ICreateSessionOptions): Promise<CoreSession> {
185-
const sessionMeta = await this.commandQueue.run<ISessionMeta>('createSession', options);
185+
const sessionMeta = await this.commandQueue.run<ISessionMeta>('Session.create', options);
186186
const session = new CoreSession({ ...sessionMeta, sessionName: options.sessionName }, this);
187187
this.coreSessions.track(session);
188188
return session;
@@ -197,7 +197,7 @@ export default abstract class ConnectionToCore extends TypedEventEmitter<{
197197
}
198198

199199
public async logUnhandledError(error: Error): Promise<void> {
200-
await this.commandQueue.run('logUnhandledError', error);
200+
await this.commandQueue.run('Core.logUnhandledError', error);
201201
}
202202

203203
protected async internalDisconnect(
@@ -237,8 +237,8 @@ export default abstract class ConnectionToCore extends TypedEventEmitter<{
237237
const { promise, id, resolve } = this.createPendingResult();
238238
const { command } = payload;
239239

240-
if (command === 'connect') this.connectRequestId = id;
241-
if (command === 'disconnect') this.disconnectRequestId = id;
240+
if (command === 'Core.connect') this.connectRequestId = id;
241+
if (command === 'Core.disconnect') this.disconnectRequestId = id;
242242

243243
let timeout: NodeJS.Timeout;
244244
if (timeoutMs) timeout = setTimeout(() => resolve(null), timeoutMs).unref();

client/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import IAgentCreateOptions from './interfaces/IAgentCreateOptions';
1010
import IConnectionToCoreOptions from './interfaces/IConnectionToCoreOptions';
1111
import Handler from './lib/Handler';
1212
import Agent from './lib/Agent';
13+
import type FrameEnvironment from './lib/FrameEnvironment';
14+
import type Tab from './lib/Tab';
1315
import RemoteConnectionToCore from './connections/RemoteConnectionToCore';
1416
import ConnectionToCore from './connections/ConnectionToCore';
1517
import ConnectionFactory from './connections/ConnectionFactory';
@@ -30,6 +32,8 @@ export {
3032
IAgentCreateOptions,
3133
IConnectionToCoreOptions,
3234
Node,
35+
FrameEnvironment,
36+
Tab,
3337
XPathResult,
3438
LocationStatus,
3539
LocationTrigger,
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import CoreSession from '../lib/CoreTab';
21
import Agent from '../lib/Agent';
2+
import CoreFrameEnvironment from '../lib/CoreFrameEnvironment';
33

44
export default interface IAwaitedOptions {
55
secretAgent: Agent;
6-
coreTab: Promise<CoreSession>;
6+
coreFrame: Promise<CoreFrameEnvironment>;
77
}

client/lib/Agent.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import IAgentConfigureOptions from '../interfaces/IAgentConfigureOptions';
3838
import ConnectionFactory from '../connections/ConnectionFactory';
3939
import ConnectionToCore from '../connections/ConnectionToCore';
4040
import DisconnectedFromCoreError from '../connections/DisconnectedFromCoreError';
41+
import FrameEnvironment, { getCoreFrameEnvironment } from './FrameEnvironment';
4142

4243
export const DefaultOptions = {
4344
defaultBlockedResourceTypes: [BlockedResourceType.None],
@@ -58,6 +59,8 @@ const propertyKeys: (keyof Agent)[] = [
5859
'sessionId',
5960
'meta',
6061
'tabs',
62+
'frameEnvironments',
63+
'mainFrameEnvironment',
6164
'coreHost',
6265
'activeTab',
6366
'sessionName',
@@ -105,10 +108,18 @@ export default class Agent extends AwaitedEventTarget<{ close: void }> {
105108
return this.activeTab.document;
106109
}
107110

111+
public get frameEnvironments(): Promise<FrameEnvironment[]> {
112+
return this.activeTab.frameEnvironments;
113+
}
114+
108115
public get lastCommandId(): Promise<number> {
109116
return this.activeTab.lastCommandId;
110117
}
111118

119+
public get mainFrameEnvironment(): FrameEnvironment {
120+
return this.activeTab.mainFrameEnvironment;
121+
}
122+
112123
public get sessionId(): Promise<string> {
113124
const { coreSession } = getState(this).connection;
114125
return coreSession.then(x => x.sessionId);
@@ -207,24 +218,30 @@ export default class Agent extends AwaitedEventTarget<{ close: void }> {
207218
// INTERACT METHODS
208219

209220
public async click(mousePosition: IMousePosition): Promise<void> {
210-
const coreTab = await getCoreTab(this.activeTab);
211-
await Interactor.run(coreTab, [{ click: mousePosition }]);
221+
const coreFrame = await getCoreFrameEnvironment(this.activeTab.mainFrameEnvironment);
222+
await Interactor.run(coreFrame, [{ click: mousePosition }]);
223+
}
224+
225+
public async getFrameEnvironment(
226+
frameElement: IElementIsolate,
227+
): Promise<FrameEnvironment | null> {
228+
return await this.activeTab.getFrameEnvironment(frameElement);
212229
}
213230

214231
public async interact(...interactions: IInteractions): Promise<void> {
215-
const coreTab = await getCoreTab(this.activeTab);
216-
await Interactor.run(coreTab, interactions);
232+
const coreFrame = await getCoreFrameEnvironment(this.activeTab.mainFrameEnvironment);
233+
await Interactor.run(coreFrame, interactions);
217234
}
218235

219236
public async scrollTo(mousePosition: IMousePosition): Promise<void> {
220-
const coreTab = await getCoreTab(this.activeTab);
221-
await Interactor.run(coreTab, [{ [Command.scroll]: mousePosition }]);
237+
const coreFrame = await getCoreFrameEnvironment(this.activeTab.mainFrameEnvironment);
238+
await Interactor.run(coreFrame, [{ [Command.scroll]: mousePosition }]);
222239
}
223240

224241
public async type(...typeInteractions: ITypeInteraction[]): Promise<void> {
225-
const coreTab = await getCoreTab(this.activeTab);
242+
const coreFrame = await getCoreFrameEnvironment(this.activeTab.mainFrameEnvironment);
226243
await Interactor.run(
227-
coreTab,
244+
coreFrame,
228245
typeInteractions.map(t => ({ type: t })),
229246
);
230247
}

client/lib/CookieStorage.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import initializeConstantsAndProperties from 'awaited-dom/base/initializeConstan
22
import StateMachine from 'awaited-dom/base/StateMachine';
33
import ISetCookieOptions from '@secret-agent/core-interfaces/ISetCookieOptions';
44
import { ICookie } from '@secret-agent/core-interfaces/ICookie';
5-
import CoreSession from './CoreTab';
5+
import CoreFrameEnvironment from './CoreFrameEnvironment';
66

77
const { getState, setState } = StateMachine<CookieStorage, IState>();
88

99
interface IState {
10-
coreTab: Promise<CoreSession>;
10+
coreFrame: Promise<CoreFrameEnvironment>;
1111
}
1212

1313
export default class CookieStorage {
@@ -20,8 +20,8 @@ export default class CookieStorage {
2020
}
2121

2222
public async getItems(): Promise<ICookie[]> {
23-
const coreTab = await getState(this).coreTab;
24-
return await coreTab.getCookies();
23+
const coreFrame = await getCoreFrame(this);
24+
return await coreFrame.getCookies();
2525
}
2626

2727
public async key(index: number): Promise<string> {
@@ -30,10 +30,10 @@ export default class CookieStorage {
3030
}
3131

3232
public async clear(): Promise<void> {
33-
const coreTab = await getState(this).coreTab;
33+
const coreFrame = await getCoreFrame(this);
3434
const cookies = await this.getItems();
3535
for (const cookie of cookies) {
36-
await coreTab.removeCookie(cookie.name);
36+
await coreFrame.removeCookie(cookie.name);
3737
}
3838
}
3939

@@ -43,18 +43,22 @@ export default class CookieStorage {
4343
}
4444

4545
public async setItem(key: string, value: string, options?: ISetCookieOptions): Promise<boolean> {
46-
const coreTab = await getState(this).coreTab;
47-
return coreTab.setCookie(key, value, options);
46+
const coreFrame = await getCoreFrame(this);
47+
return coreFrame.setCookie(key, value, options);
4848
}
4949

5050
public async removeItem(name: string): Promise<boolean> {
51-
const coreTab = await getState(this).coreTab;
52-
return coreTab.removeCookie(name);
51+
const coreFrame = await getCoreFrame(this);
52+
return coreFrame.removeCookie(name);
5353
}
5454
}
5555

56-
export function createCookieStorage(coreTab: Promise<CoreSession>): CookieStorage {
56+
function getCoreFrame(cookieStorage: CookieStorage): Promise<CoreFrameEnvironment> {
57+
return getState(cookieStorage).coreFrame;
58+
}
59+
60+
export function createCookieStorage(coreFrame: Promise<CoreFrameEnvironment>): CookieStorage {
5761
const cookieStorage = new CookieStorage();
58-
setState(cookieStorage, { coreTab });
62+
setState(cookieStorage, { coreFrame });
5963
return cookieStorage;
6064
}

client/lib/CoreCommandQueue.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default class CoreCommandQueue {
1212
constructor(
1313
private readonly meta: (ISessionMeta & { sessionName: string }) | null,
1414
private readonly connection: ConnectionToCore,
15+
sharedQueue?: Queue,
1516
) {
1617
if (meta) {
1718
const markers = [
@@ -22,7 +23,8 @@ export default class CoreCommandQueue {
2223
].join('\n');
2324
this.sessionMarker = `\n\n${markers}`;
2425
}
25-
this.internalQueue = new Queue('CORE COMMANDS');
26+
27+
this.internalQueue = sharedQueue ?? new Queue('CORE COMMANDS');
2628
this.internalQueue.concurrency = 1;
2729
}
2830

@@ -37,6 +39,10 @@ export default class CoreCommandQueue {
3739
this.internalQueue.stop(cancelError);
3840
}
3941

42+
public createSharedQueue(meta: ISessionMeta & { sessionName: string }): CoreCommandQueue {
43+
return new CoreCommandQueue(meta, this.connection, this.internalQueue);
44+
}
45+
4046
private async runRequest<T>(command: string, args: any[]): Promise<T> {
4147
const response = await this.connection.sendRequest({
4248
meta: this.meta,

client/lib/CoreEventHeap.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export default class CoreEventHeap {
4242

4343
const subscriptionPromise = this.connection.sendRequest({
4444
meta: this.meta,
45-
command: 'addEventListener',
45+
command: 'Session.addEventListener',
4646
args: [jsPath, type, options],
4747
});
4848

@@ -78,7 +78,7 @@ export default class CoreEventHeap {
7878
this.connection
7979
.sendRequest({
8080
meta: this.meta,
81-
command: 'removeEventListener',
81+
command: 'Session.removeEventListener',
8282
args: [listenerId],
8383
})
8484
.catch(error => {

client/lib/CoreFrameEnvironment.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { IInteractionGroups } from '@secret-agent/core-interfaces/IInteractions';
2+
import ISessionMeta from '@secret-agent/core-interfaces/ISessionMeta';
3+
import { ILocationStatus, ILocationTrigger } from '@secret-agent/core-interfaces/Location';
4+
import { IJsPath } from 'awaited-dom/base/AwaitedPath';
5+
import { ICookie } from '@secret-agent/core-interfaces/ICookie';
6+
import IWaitForElementOptions from '@secret-agent/core-interfaces/IWaitForElementOptions';
7+
import IExecJsPathResult from '@secret-agent/core-interfaces/IExecJsPathResult';
8+
import { IRequestInit } from 'awaited-dom/base/interfaces/official';
9+
import IAttachedState from 'awaited-dom/base/IAttachedState';
10+
import ISetCookieOptions from '@secret-agent/core-interfaces/ISetCookieOptions';
11+
import IWaitForOptions from '@secret-agent/core-interfaces/IWaitForOptions';
12+
import IFrameMeta from '@secret-agent/core-interfaces/IFrameMeta';
13+
import CoreCommandQueue from './CoreCommandQueue';
14+
15+
export default class CoreFrameEnvironment {
16+
public tabId: number;
17+
public frameId: string;
18+
public sessionId: string;
19+
public commandQueue: CoreCommandQueue;
20+
21+
constructor(meta: ISessionMeta & { sessionName: string }, commandQueue: CoreCommandQueue) {
22+
const { tabId, sessionId, frameId, sessionName } = meta;
23+
this.tabId = tabId;
24+
this.sessionId = sessionId;
25+
this.frameId = frameId;
26+
const queueMeta = {
27+
sessionId,
28+
tabId,
29+
sessionName,
30+
frameId,
31+
};
32+
this.commandQueue = commandQueue.createSharedQueue(queueMeta);
33+
}
34+
35+
public async getFrameMeta(): Promise<IFrameMeta> {
36+
return await this.commandQueue.run('FrameEnvironment.meta');
37+
}
38+
39+
public async getChildFrameEnvironment(jsPath: IJsPath): Promise<IFrameMeta> {
40+
return await this.commandQueue.run('FrameEnvironment.getChildFrameEnvironment', jsPath);
41+
}
42+
43+
public async execJsPath<T = any>(jsPath: IJsPath): Promise<IExecJsPathResult<T>> {
44+
return await this.commandQueue.run('FrameEnvironment.execJsPath', jsPath);
45+
}
46+
47+
public async getJsValue<T>(expression: string): Promise<{ value: T; type: string }> {
48+
return await this.commandQueue.run('FrameEnvironment.getJsValue', expression);
49+
}
50+
51+
public async fetch(request: string | number, init?: IRequestInit): Promise<IAttachedState> {
52+
return await this.commandQueue.run('FrameEnvironment.fetch', request, init);
53+
}
54+
55+
public async createRequest(input: string | number, init?: IRequestInit): Promise<IAttachedState> {
56+
return await this.commandQueue.run('FrameEnvironment.createRequest', input, init);
57+
}
58+
59+
public async getUrl(): Promise<string> {
60+
return await this.commandQueue.run('FrameEnvironment.getLocationHref');
61+
}
62+
63+
public async interact(interactionGroups: IInteractionGroups): Promise<void> {
64+
await this.commandQueue.run('FrameEnvironment.interact', ...interactionGroups);
65+
}
66+
67+
public async getCookies(): Promise<ICookie[]> {
68+
return await this.commandQueue.run('FrameEnvironment.getCookies');
69+
}
70+
71+
public async setCookie(
72+
name: string,
73+
value: string,
74+
options?: ISetCookieOptions,
75+
): Promise<boolean> {
76+
return await this.commandQueue.run('FrameEnvironment.setCookie', name, value, options);
77+
}
78+
79+
public async removeCookie(name: string): Promise<boolean> {
80+
return await this.commandQueue.run('FrameEnvironment.removeCookie', name);
81+
}
82+
83+
public async isElementVisible(jsPath: IJsPath): Promise<boolean> {
84+
return await this.commandQueue.run('FrameEnvironment.isElementVisible', jsPath);
85+
}
86+
87+
public async waitForElement(jsPath: IJsPath, opts: IWaitForElementOptions): Promise<void> {
88+
await this.commandQueue.run('FrameEnvironment.waitForElement', jsPath, opts);
89+
}
90+
91+
public async waitForLoad(status: ILocationStatus, opts: IWaitForOptions): Promise<void> {
92+
await this.commandQueue.run('FrameEnvironment.waitForLoad', status, opts);
93+
}
94+
95+
public async waitForLocation(trigger: ILocationTrigger, opts: IWaitForOptions): Promise<void> {
96+
await this.commandQueue.run('FrameEnvironment.waitForLocation', trigger, opts);
97+
}
98+
}

0 commit comments

Comments
 (0)