browser-pilot 0.0.1
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/LICENSE +21 -0
- package/README.md +539 -0
- package/dist/actions.cjs +277 -0
- package/dist/actions.d.cts +33 -0
- package/dist/actions.d.ts +33 -0
- package/dist/actions.mjs +8 -0
- package/dist/browser.cjs +2765 -0
- package/dist/browser.d.cts +71 -0
- package/dist/browser.d.ts +71 -0
- package/dist/browser.mjs +19 -0
- package/dist/cdp.cjs +279 -0
- package/dist/cdp.d.cts +230 -0
- package/dist/cdp.d.ts +230 -0
- package/dist/cdp.mjs +10 -0
- package/dist/chunk-BCOZUKWS.mjs +251 -0
- package/dist/chunk-FI55U7JS.mjs +2108 -0
- package/dist/chunk-R3PS4PCM.mjs +207 -0
- package/dist/chunk-YEHK2XY3.mjs +250 -0
- package/dist/chunk-ZIQA4JOT.mjs +226 -0
- package/dist/cli.cjs +3587 -0
- package/dist/cli.d.cts +23 -0
- package/dist/cli.d.ts +23 -0
- package/dist/cli.mjs +827 -0
- package/dist/client-7Nqka5MV.d.cts +53 -0
- package/dist/client-7Nqka5MV.d.ts +53 -0
- package/dist/index.cjs +3074 -0
- package/dist/index.d.cts +157 -0
- package/dist/index.d.ts +157 -0
- package/dist/index.mjs +64 -0
- package/dist/providers.cjs +238 -0
- package/dist/providers.d.cts +86 -0
- package/dist/providers.d.ts +86 -0
- package/dist/providers.mjs +16 -0
- package/dist/types-Cs89wle0.d.cts +925 -0
- package/dist/types-DL_-3BZk.d.ts +925 -0
- package/dist/types-D_uDqh0Z.d.cts +56 -0
- package/dist/types-D_uDqh0Z.d.ts +56 -0
- package/package.json +91 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// src/providers/browserbase.ts
|
|
2
|
+
var BrowserBaseProvider = class {
|
|
3
|
+
name = "browserbase";
|
|
4
|
+
apiKey;
|
|
5
|
+
projectId;
|
|
6
|
+
baseUrl;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.apiKey = options.apiKey;
|
|
9
|
+
this.projectId = options.projectId;
|
|
10
|
+
this.baseUrl = options.baseUrl ?? "https://api.browserbase.com";
|
|
11
|
+
}
|
|
12
|
+
async createSession(options = {}) {
|
|
13
|
+
const response = await fetch(`${this.baseUrl}/v1/sessions`, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: {
|
|
16
|
+
"X-BB-API-Key": this.apiKey,
|
|
17
|
+
"Content-Type": "application/json"
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify({
|
|
20
|
+
projectId: this.projectId,
|
|
21
|
+
browserSettings: {
|
|
22
|
+
viewport: options.width && options.height ? {
|
|
23
|
+
width: options.width,
|
|
24
|
+
height: options.height
|
|
25
|
+
} : void 0
|
|
26
|
+
},
|
|
27
|
+
...options
|
|
28
|
+
})
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
const text = await response.text();
|
|
32
|
+
throw new Error(`BrowserBase createSession failed: ${response.status} ${text}`);
|
|
33
|
+
}
|
|
34
|
+
const session = await response.json();
|
|
35
|
+
const connectResponse = await fetch(`${this.baseUrl}/v1/sessions/${session.id}`, {
|
|
36
|
+
headers: {
|
|
37
|
+
"X-BB-API-Key": this.apiKey
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
if (!connectResponse.ok) {
|
|
41
|
+
throw new Error(`BrowserBase getSession failed: ${connectResponse.status}`);
|
|
42
|
+
}
|
|
43
|
+
const sessionDetails = await connectResponse.json();
|
|
44
|
+
if (!sessionDetails.connectUrl) {
|
|
45
|
+
throw new Error("BrowserBase session does not have a connectUrl");
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
wsUrl: sessionDetails.connectUrl,
|
|
49
|
+
sessionId: session.id,
|
|
50
|
+
metadata: {
|
|
51
|
+
debugUrl: sessionDetails.debugUrl,
|
|
52
|
+
projectId: this.projectId,
|
|
53
|
+
status: sessionDetails.status
|
|
54
|
+
},
|
|
55
|
+
close: async () => {
|
|
56
|
+
await fetch(`${this.baseUrl}/v1/sessions/${session.id}`, {
|
|
57
|
+
method: "DELETE",
|
|
58
|
+
headers: {
|
|
59
|
+
"X-BB-API-Key": this.apiKey
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async resumeSession(sessionId) {
|
|
66
|
+
const response = await fetch(`${this.baseUrl}/v1/sessions/${sessionId}`, {
|
|
67
|
+
headers: {
|
|
68
|
+
"X-BB-API-Key": this.apiKey
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new Error(`BrowserBase resumeSession failed: ${response.status}`);
|
|
73
|
+
}
|
|
74
|
+
const session = await response.json();
|
|
75
|
+
if (!session.connectUrl) {
|
|
76
|
+
throw new Error("BrowserBase session does not have a connectUrl (may be closed)");
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
wsUrl: session.connectUrl,
|
|
80
|
+
sessionId: session.id,
|
|
81
|
+
metadata: {
|
|
82
|
+
debugUrl: session.debugUrl,
|
|
83
|
+
projectId: this.projectId,
|
|
84
|
+
status: session.status
|
|
85
|
+
},
|
|
86
|
+
close: async () => {
|
|
87
|
+
await fetch(`${this.baseUrl}/v1/sessions/${sessionId}`, {
|
|
88
|
+
method: "DELETE",
|
|
89
|
+
headers: {
|
|
90
|
+
"X-BB-API-Key": this.apiKey
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/providers/browserless.ts
|
|
99
|
+
var BrowserlessProvider = class {
|
|
100
|
+
name = "browserless";
|
|
101
|
+
token;
|
|
102
|
+
baseUrl;
|
|
103
|
+
constructor(options) {
|
|
104
|
+
this.token = options.token;
|
|
105
|
+
this.baseUrl = options.baseUrl ?? "wss://chrome.browserless.io";
|
|
106
|
+
}
|
|
107
|
+
async createSession(options = {}) {
|
|
108
|
+
const params = new URLSearchParams({
|
|
109
|
+
token: this.token
|
|
110
|
+
});
|
|
111
|
+
if (options.width && options.height) {
|
|
112
|
+
params.set("--window-size", `${options.width},${options.height}`);
|
|
113
|
+
}
|
|
114
|
+
if (options.proxy?.server) {
|
|
115
|
+
params.set("--proxy-server", options.proxy.server);
|
|
116
|
+
}
|
|
117
|
+
const wsUrl = `${this.baseUrl}?${params.toString()}`;
|
|
118
|
+
return {
|
|
119
|
+
wsUrl,
|
|
120
|
+
metadata: {
|
|
121
|
+
provider: "browserless"
|
|
122
|
+
},
|
|
123
|
+
close: async () => {
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Browserless doesn't support session resumption in the same way
|
|
128
|
+
// Each connection is a fresh browser instance
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/providers/generic.ts
|
|
132
|
+
var GenericProvider = class {
|
|
133
|
+
name = "generic";
|
|
134
|
+
wsUrl;
|
|
135
|
+
constructor(options) {
|
|
136
|
+
this.wsUrl = options.wsUrl;
|
|
137
|
+
}
|
|
138
|
+
async createSession(_options = {}) {
|
|
139
|
+
return {
|
|
140
|
+
wsUrl: this.wsUrl,
|
|
141
|
+
metadata: {
|
|
142
|
+
provider: "generic"
|
|
143
|
+
},
|
|
144
|
+
close: async () => {
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
async function discoverTargets(host = "localhost:9222") {
|
|
150
|
+
const protocol = host.includes("://") ? "" : "http://";
|
|
151
|
+
const response = await fetch(`${protocol}${host}/json/list`);
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
throw new Error(`Failed to discover targets: ${response.status}`);
|
|
154
|
+
}
|
|
155
|
+
return await response.json();
|
|
156
|
+
}
|
|
157
|
+
async function getBrowserWebSocketUrl(host = "localhost:9222") {
|
|
158
|
+
const protocol = host.includes("://") ? "" : "http://";
|
|
159
|
+
const response = await fetch(`${protocol}${host}/json/version`);
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
throw new Error(`Failed to get browser info: ${response.status}`);
|
|
162
|
+
}
|
|
163
|
+
const info = await response.json();
|
|
164
|
+
return info.webSocketDebuggerUrl;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/providers/index.ts
|
|
168
|
+
function createProvider(options) {
|
|
169
|
+
switch (options.provider) {
|
|
170
|
+
case "browserbase":
|
|
171
|
+
if (!options.apiKey) {
|
|
172
|
+
throw new Error("BrowserBase provider requires apiKey");
|
|
173
|
+
}
|
|
174
|
+
if (!options.projectId) {
|
|
175
|
+
throw new Error("BrowserBase provider requires projectId");
|
|
176
|
+
}
|
|
177
|
+
return new BrowserBaseProvider({
|
|
178
|
+
apiKey: options.apiKey,
|
|
179
|
+
projectId: options.projectId
|
|
180
|
+
});
|
|
181
|
+
case "browserless":
|
|
182
|
+
if (!options.apiKey) {
|
|
183
|
+
throw new Error("Browserless provider requires apiKey (token)");
|
|
184
|
+
}
|
|
185
|
+
return new BrowserlessProvider({
|
|
186
|
+
token: options.apiKey
|
|
187
|
+
});
|
|
188
|
+
case "generic":
|
|
189
|
+
if (!options.wsUrl) {
|
|
190
|
+
throw new Error("Generic provider requires wsUrl");
|
|
191
|
+
}
|
|
192
|
+
return new GenericProvider({
|
|
193
|
+
wsUrl: options.wsUrl
|
|
194
|
+
});
|
|
195
|
+
default:
|
|
196
|
+
throw new Error(`Unknown provider: ${options.provider}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export {
|
|
201
|
+
BrowserBaseProvider,
|
|
202
|
+
BrowserlessProvider,
|
|
203
|
+
GenericProvider,
|
|
204
|
+
discoverTargets,
|
|
205
|
+
getBrowserWebSocketUrl,
|
|
206
|
+
createProvider
|
|
207
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// src/actions/executor.ts
|
|
2
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
3
|
+
var BatchExecutor = class {
|
|
4
|
+
page;
|
|
5
|
+
constructor(page) {
|
|
6
|
+
this.page = page;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Execute a batch of steps
|
|
10
|
+
*/
|
|
11
|
+
async execute(steps, options = {}) {
|
|
12
|
+
const { timeout = DEFAULT_TIMEOUT, onFail = "stop" } = options;
|
|
13
|
+
const results = [];
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
for (let i = 0; i < steps.length; i++) {
|
|
16
|
+
const step = steps[i];
|
|
17
|
+
const stepStart = Date.now();
|
|
18
|
+
try {
|
|
19
|
+
const result = await this.executeStep(step, timeout);
|
|
20
|
+
results.push({
|
|
21
|
+
index: i,
|
|
22
|
+
action: step.action,
|
|
23
|
+
selector: step.selector,
|
|
24
|
+
selectorUsed: result.selectorUsed,
|
|
25
|
+
success: true,
|
|
26
|
+
durationMs: Date.now() - stepStart,
|
|
27
|
+
result: result.value,
|
|
28
|
+
text: result.text
|
|
29
|
+
});
|
|
30
|
+
} catch (error) {
|
|
31
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
32
|
+
results.push({
|
|
33
|
+
index: i,
|
|
34
|
+
action: step.action,
|
|
35
|
+
selector: step.selector,
|
|
36
|
+
success: false,
|
|
37
|
+
durationMs: Date.now() - stepStart,
|
|
38
|
+
error: errorMessage
|
|
39
|
+
});
|
|
40
|
+
if (onFail === "stop" && !step.optional) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
stoppedAtIndex: i,
|
|
44
|
+
steps: results,
|
|
45
|
+
totalDurationMs: Date.now() - startTime
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const allSuccess = results.every((r) => r.success || steps[r.index]?.optional);
|
|
51
|
+
return {
|
|
52
|
+
success: allSuccess,
|
|
53
|
+
steps: results,
|
|
54
|
+
totalDurationMs: Date.now() - startTime
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Execute a single step
|
|
59
|
+
*/
|
|
60
|
+
async executeStep(step, defaultTimeout) {
|
|
61
|
+
const timeout = step.timeout ?? defaultTimeout;
|
|
62
|
+
const optional = step.optional ?? false;
|
|
63
|
+
switch (step.action) {
|
|
64
|
+
case "goto": {
|
|
65
|
+
if (!step.url) throw new Error("goto requires url");
|
|
66
|
+
await this.page.goto(step.url, { timeout, optional });
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
case "click": {
|
|
70
|
+
if (!step.selector) throw new Error("click requires selector");
|
|
71
|
+
if (step.waitForNavigation) {
|
|
72
|
+
const navPromise = this.page.waitForNavigation({ timeout, optional });
|
|
73
|
+
await this.page.click(step.selector, { timeout, optional });
|
|
74
|
+
await navPromise;
|
|
75
|
+
} else {
|
|
76
|
+
await this.page.click(step.selector, { timeout, optional });
|
|
77
|
+
}
|
|
78
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
79
|
+
}
|
|
80
|
+
case "fill": {
|
|
81
|
+
if (!step.selector) throw new Error("fill requires selector");
|
|
82
|
+
if (typeof step.value !== "string") throw new Error("fill requires string value");
|
|
83
|
+
await this.page.fill(step.selector, step.value, {
|
|
84
|
+
timeout,
|
|
85
|
+
optional,
|
|
86
|
+
clear: step.clear ?? true
|
|
87
|
+
});
|
|
88
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
89
|
+
}
|
|
90
|
+
case "type": {
|
|
91
|
+
if (!step.selector) throw new Error("type requires selector");
|
|
92
|
+
if (typeof step.value !== "string") throw new Error("type requires string value");
|
|
93
|
+
await this.page.type(step.selector, step.value, {
|
|
94
|
+
timeout,
|
|
95
|
+
optional,
|
|
96
|
+
delay: step.delay ?? 50
|
|
97
|
+
});
|
|
98
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
99
|
+
}
|
|
100
|
+
case "select": {
|
|
101
|
+
if (step.trigger && step.option && typeof step.value === "string") {
|
|
102
|
+
await this.page.select(
|
|
103
|
+
{
|
|
104
|
+
trigger: step.trigger,
|
|
105
|
+
option: step.option,
|
|
106
|
+
value: step.value,
|
|
107
|
+
match: step.match
|
|
108
|
+
},
|
|
109
|
+
{ timeout, optional }
|
|
110
|
+
);
|
|
111
|
+
return { selectorUsed: this.getUsedSelector(step.trigger) };
|
|
112
|
+
}
|
|
113
|
+
if (!step.selector) throw new Error("select requires selector");
|
|
114
|
+
if (!step.value) throw new Error("select requires value");
|
|
115
|
+
await this.page.select(step.selector, step.value, { timeout, optional });
|
|
116
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
117
|
+
}
|
|
118
|
+
case "check": {
|
|
119
|
+
if (!step.selector) throw new Error("check requires selector");
|
|
120
|
+
await this.page.check(step.selector, { timeout, optional });
|
|
121
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
122
|
+
}
|
|
123
|
+
case "uncheck": {
|
|
124
|
+
if (!step.selector) throw new Error("uncheck requires selector");
|
|
125
|
+
await this.page.uncheck(step.selector, { timeout, optional });
|
|
126
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
127
|
+
}
|
|
128
|
+
case "submit": {
|
|
129
|
+
if (!step.selector) throw new Error("submit requires selector");
|
|
130
|
+
await this.page.submit(step.selector, {
|
|
131
|
+
timeout,
|
|
132
|
+
optional,
|
|
133
|
+
method: step.method ?? "enter+click"
|
|
134
|
+
});
|
|
135
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
136
|
+
}
|
|
137
|
+
case "press": {
|
|
138
|
+
if (!step.key) throw new Error("press requires key");
|
|
139
|
+
await this.page.press(step.key);
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
case "focus": {
|
|
143
|
+
if (!step.selector) throw new Error("focus requires selector");
|
|
144
|
+
await this.page.focus(step.selector, { timeout, optional });
|
|
145
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
146
|
+
}
|
|
147
|
+
case "hover": {
|
|
148
|
+
if (!step.selector) throw new Error("hover requires selector");
|
|
149
|
+
await this.page.hover(step.selector, { timeout, optional });
|
|
150
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
151
|
+
}
|
|
152
|
+
case "scroll": {
|
|
153
|
+
if (step.x !== void 0 || step.y !== void 0) {
|
|
154
|
+
await this.page.scroll("body", { x: step.x, y: step.y, timeout, optional });
|
|
155
|
+
return {};
|
|
156
|
+
}
|
|
157
|
+
if (!step.selector && (step.direction || step.amount !== void 0)) {
|
|
158
|
+
const amount = step.amount ?? 500;
|
|
159
|
+
const direction = step.direction ?? "down";
|
|
160
|
+
const deltaY = direction === "down" ? amount : direction === "up" ? -amount : 0;
|
|
161
|
+
const deltaX = direction === "right" ? amount : direction === "left" ? -amount : 0;
|
|
162
|
+
await this.page.evaluate(`window.scrollBy(${deltaX}, ${deltaY})`);
|
|
163
|
+
return {};
|
|
164
|
+
}
|
|
165
|
+
if (!step.selector) throw new Error("scroll requires selector, coordinates, or direction");
|
|
166
|
+
await this.page.scroll(step.selector, { timeout, optional });
|
|
167
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
168
|
+
}
|
|
169
|
+
case "wait": {
|
|
170
|
+
if (!step.selector && !step.waitFor) {
|
|
171
|
+
const delay = step.timeout ?? 1e3;
|
|
172
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
173
|
+
return {};
|
|
174
|
+
}
|
|
175
|
+
if (step.waitFor === "navigation") {
|
|
176
|
+
await this.page.waitForNavigation({ timeout, optional });
|
|
177
|
+
return {};
|
|
178
|
+
}
|
|
179
|
+
if (step.waitFor === "networkIdle") {
|
|
180
|
+
await this.page.waitForNetworkIdle({ timeout, optional });
|
|
181
|
+
return {};
|
|
182
|
+
}
|
|
183
|
+
if (!step.selector)
|
|
184
|
+
throw new Error(
|
|
185
|
+
"wait requires selector (or waitFor: navigation/networkIdle, or timeout for simple delay)"
|
|
186
|
+
);
|
|
187
|
+
await this.page.waitFor(step.selector, {
|
|
188
|
+
timeout,
|
|
189
|
+
optional,
|
|
190
|
+
state: step.waitFor ?? "visible"
|
|
191
|
+
});
|
|
192
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
193
|
+
}
|
|
194
|
+
case "snapshot": {
|
|
195
|
+
const snapshot = await this.page.snapshot();
|
|
196
|
+
return { value: snapshot };
|
|
197
|
+
}
|
|
198
|
+
case "screenshot": {
|
|
199
|
+
const data = await this.page.screenshot({
|
|
200
|
+
format: step.format,
|
|
201
|
+
quality: step.quality,
|
|
202
|
+
fullPage: step.fullPage
|
|
203
|
+
});
|
|
204
|
+
return { value: data };
|
|
205
|
+
}
|
|
206
|
+
case "evaluate": {
|
|
207
|
+
if (typeof step.value !== "string")
|
|
208
|
+
throw new Error("evaluate requires string value (expression)");
|
|
209
|
+
const result = await this.page.evaluate(step.value);
|
|
210
|
+
return { value: result };
|
|
211
|
+
}
|
|
212
|
+
case "text": {
|
|
213
|
+
const selector = Array.isArray(step.selector) ? step.selector[0] : step.selector;
|
|
214
|
+
const text = await this.page.text(selector);
|
|
215
|
+
return { text, selectorUsed: selector };
|
|
216
|
+
}
|
|
217
|
+
case "switchFrame": {
|
|
218
|
+
if (!step.selector) throw new Error("switchFrame requires selector");
|
|
219
|
+
await this.page.switchToFrame(step.selector, { timeout, optional });
|
|
220
|
+
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
221
|
+
}
|
|
222
|
+
case "switchToMain": {
|
|
223
|
+
await this.page.switchToMain();
|
|
224
|
+
return {};
|
|
225
|
+
}
|
|
226
|
+
default:
|
|
227
|
+
throw new Error(
|
|
228
|
+
`Unknown action: ${step.action}. Run 'bp actions' for available actions.`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get the first selector if multiple were provided
|
|
234
|
+
* (actual used selector tracking would need to be implemented in Page)
|
|
235
|
+
*/
|
|
236
|
+
getUsedSelector(selector) {
|
|
237
|
+
return Array.isArray(selector) ? selector[0] : selector;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
function addBatchToPage(page) {
|
|
241
|
+
const executor = new BatchExecutor(page);
|
|
242
|
+
return Object.assign(page, {
|
|
243
|
+
batch: (steps, options) => executor.execute(steps, options)
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export {
|
|
248
|
+
BatchExecutor,
|
|
249
|
+
addBatchToPage
|
|
250
|
+
};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// src/emulation/devices.ts
|
|
2
|
+
var devices = {
|
|
3
|
+
"iPhone 14": {
|
|
4
|
+
name: "iPhone 14",
|
|
5
|
+
viewport: {
|
|
6
|
+
width: 390,
|
|
7
|
+
height: 844,
|
|
8
|
+
deviceScaleFactor: 3,
|
|
9
|
+
isMobile: true,
|
|
10
|
+
hasTouch: true
|
|
11
|
+
},
|
|
12
|
+
userAgent: {
|
|
13
|
+
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
|
|
14
|
+
platform: "iPhone",
|
|
15
|
+
userAgentMetadata: {
|
|
16
|
+
mobile: true,
|
|
17
|
+
platform: "iOS",
|
|
18
|
+
platformVersion: "16.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"iPhone 14 Pro Max": {
|
|
23
|
+
name: "iPhone 14 Pro Max",
|
|
24
|
+
viewport: {
|
|
25
|
+
width: 430,
|
|
26
|
+
height: 932,
|
|
27
|
+
deviceScaleFactor: 3,
|
|
28
|
+
isMobile: true,
|
|
29
|
+
hasTouch: true
|
|
30
|
+
},
|
|
31
|
+
userAgent: {
|
|
32
|
+
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
|
|
33
|
+
platform: "iPhone"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"Pixel 7": {
|
|
37
|
+
name: "Pixel 7",
|
|
38
|
+
viewport: {
|
|
39
|
+
width: 412,
|
|
40
|
+
height: 915,
|
|
41
|
+
deviceScaleFactor: 2.625,
|
|
42
|
+
isMobile: true,
|
|
43
|
+
hasTouch: true
|
|
44
|
+
},
|
|
45
|
+
userAgent: {
|
|
46
|
+
userAgent: "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
|
|
47
|
+
platform: "Linux armv8l",
|
|
48
|
+
userAgentMetadata: {
|
|
49
|
+
mobile: true,
|
|
50
|
+
platform: "Android",
|
|
51
|
+
platformVersion: "13",
|
|
52
|
+
model: "Pixel 7"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"iPad Pro 11": {
|
|
57
|
+
name: "iPad Pro 11",
|
|
58
|
+
viewport: {
|
|
59
|
+
width: 834,
|
|
60
|
+
height: 1194,
|
|
61
|
+
deviceScaleFactor: 2,
|
|
62
|
+
isMobile: true,
|
|
63
|
+
hasTouch: true
|
|
64
|
+
},
|
|
65
|
+
userAgent: {
|
|
66
|
+
userAgent: "Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
|
|
67
|
+
platform: "iPad"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"Desktop Chrome": {
|
|
71
|
+
name: "Desktop Chrome",
|
|
72
|
+
viewport: {
|
|
73
|
+
width: 1920,
|
|
74
|
+
height: 1080,
|
|
75
|
+
deviceScaleFactor: 1,
|
|
76
|
+
isMobile: false,
|
|
77
|
+
hasTouch: false
|
|
78
|
+
},
|
|
79
|
+
userAgent: {
|
|
80
|
+
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
81
|
+
platform: "Win32",
|
|
82
|
+
userAgentMetadata: {
|
|
83
|
+
brands: [
|
|
84
|
+
{ brand: "Not_A Brand", version: "8" },
|
|
85
|
+
{ brand: "Chromium", version: "120" },
|
|
86
|
+
{ brand: "Google Chrome", version: "120" }
|
|
87
|
+
],
|
|
88
|
+
platform: "Windows",
|
|
89
|
+
platformVersion: "10.0.0",
|
|
90
|
+
architecture: "x86",
|
|
91
|
+
bitness: "64",
|
|
92
|
+
mobile: false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"Desktop Firefox": {
|
|
97
|
+
name: "Desktop Firefox",
|
|
98
|
+
viewport: {
|
|
99
|
+
width: 1920,
|
|
100
|
+
height: 1080,
|
|
101
|
+
deviceScaleFactor: 1,
|
|
102
|
+
isMobile: false,
|
|
103
|
+
hasTouch: false
|
|
104
|
+
},
|
|
105
|
+
userAgent: {
|
|
106
|
+
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
|
|
107
|
+
platform: "Win32"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/trace/tracer.ts
|
|
113
|
+
var LEVEL_ORDER = {
|
|
114
|
+
debug: 0,
|
|
115
|
+
info: 1,
|
|
116
|
+
warn: 2,
|
|
117
|
+
error: 3
|
|
118
|
+
};
|
|
119
|
+
var Tracer = class _Tracer {
|
|
120
|
+
options;
|
|
121
|
+
constructor(options = {}) {
|
|
122
|
+
this.options = {
|
|
123
|
+
enabled: options.enabled ?? false,
|
|
124
|
+
output: options.output ?? "console",
|
|
125
|
+
callback: options.callback,
|
|
126
|
+
level: options.level ?? "info",
|
|
127
|
+
includeTimings: options.includeTimings ?? true
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Emit a trace event
|
|
132
|
+
*/
|
|
133
|
+
emit(event) {
|
|
134
|
+
if (!this.options.enabled) return;
|
|
135
|
+
if (LEVEL_ORDER[event.level] < LEVEL_ORDER[this.options.level]) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const fullEvent = {
|
|
139
|
+
...event,
|
|
140
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
141
|
+
};
|
|
142
|
+
switch (this.options.output) {
|
|
143
|
+
case "console":
|
|
144
|
+
this.logToConsole(fullEvent);
|
|
145
|
+
break;
|
|
146
|
+
case "callback":
|
|
147
|
+
this.options.callback?.(fullEvent);
|
|
148
|
+
break;
|
|
149
|
+
case "silent":
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Log event to console
|
|
155
|
+
*/
|
|
156
|
+
logToConsole(event) {
|
|
157
|
+
const { level, category, action, selectorUsed, success, durationMs, error } = event;
|
|
158
|
+
const icon = success === true ? "\u2713" : success === false ? "\u2717" : "\u25CB";
|
|
159
|
+
const timing = this.options.includeTimings && durationMs !== void 0 ? ` (${durationMs}ms)` : "";
|
|
160
|
+
const selector = selectorUsed ? ` ${selectorUsed}` : "";
|
|
161
|
+
const errorStr = error ? ` - ${error}` : "";
|
|
162
|
+
const message = `[${level.toUpperCase()}] [${category}] ${icon} ${action}${selector}${timing}${errorStr}`;
|
|
163
|
+
switch (level) {
|
|
164
|
+
case "debug":
|
|
165
|
+
console.debug(message);
|
|
166
|
+
break;
|
|
167
|
+
case "info":
|
|
168
|
+
console.info(message);
|
|
169
|
+
break;
|
|
170
|
+
case "warn":
|
|
171
|
+
console.warn(message);
|
|
172
|
+
break;
|
|
173
|
+
case "error":
|
|
174
|
+
console.error(message);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Create a child tracer with modified options
|
|
180
|
+
*/
|
|
181
|
+
child(options) {
|
|
182
|
+
return new _Tracer({ ...this.options, ...options });
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Enable tracing
|
|
186
|
+
*/
|
|
187
|
+
enable() {
|
|
188
|
+
this.options.enabled = true;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Disable tracing
|
|
192
|
+
*/
|
|
193
|
+
disable() {
|
|
194
|
+
this.options.enabled = false;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Check if tracing is enabled
|
|
198
|
+
*/
|
|
199
|
+
get isEnabled() {
|
|
200
|
+
return this.options.enabled;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
var globalTracer = null;
|
|
204
|
+
function getTracer() {
|
|
205
|
+
if (!globalTracer) {
|
|
206
|
+
globalTracer = new Tracer({ enabled: false });
|
|
207
|
+
}
|
|
208
|
+
return globalTracer;
|
|
209
|
+
}
|
|
210
|
+
function enableTracing(options = {}) {
|
|
211
|
+
globalTracer = new Tracer({ ...options, enabled: true });
|
|
212
|
+
return globalTracer;
|
|
213
|
+
}
|
|
214
|
+
function disableTracing() {
|
|
215
|
+
if (globalTracer) {
|
|
216
|
+
globalTracer.disable();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export {
|
|
221
|
+
devices,
|
|
222
|
+
Tracer,
|
|
223
|
+
getTracer,
|
|
224
|
+
enableTracing,
|
|
225
|
+
disableTracing
|
|
226
|
+
};
|