@browserless.io/browserless 2.24.3 → 2.25.0

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.
Files changed (160) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +1 -1
  3. package/build/browserless.js +3 -1
  4. package/build/browsers/browsers.cdp.d.ts +3 -0
  5. package/build/browsers/browsers.cdp.js +4 -1
  6. package/build/browsers/browsers.playwright.d.ts +4 -0
  7. package/build/browsers/browsers.playwright.js +5 -1
  8. package/build/browsers/index.js +3 -2
  9. package/build/http.d.ts +9 -0
  10. package/build/http.js +9 -0
  11. package/build/routes/chrome/http/content.post.body.json +8 -8
  12. package/build/routes/chrome/http/pdf.post.body.json +8 -8
  13. package/build/routes/chrome/http/scrape.post.body.json +8 -8
  14. package/build/routes/chrome/http/screenshot.post.body.json +8 -8
  15. package/build/routes/chrome/tests/kill-sessions.spec.js +1 -1
  16. package/build/routes/chromium/http/content.post.body.json +8 -8
  17. package/build/routes/chromium/http/pdf.post.body.json +8 -8
  18. package/build/routes/chromium/http/scrape.post.body.json +8 -8
  19. package/build/routes/chromium/http/screenshot.post.body.json +8 -8
  20. package/build/routes/chromium/tests/kill-sessions.spec.js +1 -1
  21. package/build/routes/edge/http/content.post.body.json +579 -0
  22. package/build/routes/edge/http/content.post.d.ts +8 -0
  23. package/build/routes/edge/http/content.post.js +7 -0
  24. package/build/routes/edge/http/content.post.query.json +183 -0
  25. package/build/routes/edge/http/content.post.response.json +5 -0
  26. package/build/routes/edge/http/download.post.body.json +32 -0
  27. package/build/routes/edge/http/download.post.d.ts +8 -0
  28. package/build/routes/edge/http/download.post.js +7 -0
  29. package/build/routes/edge/http/download.post.query.json +120 -0
  30. package/build/routes/edge/http/download.post.response.json +4 -0
  31. package/build/routes/edge/http/function.post.body.json +32 -0
  32. package/build/routes/edge/http/function.post.d.ts +8 -0
  33. package/build/routes/edge/http/function.post.js +7 -0
  34. package/build/routes/edge/http/function.post.query.json +120 -0
  35. package/build/routes/edge/http/function.post.response.json +4 -0
  36. package/build/routes/edge/http/json-list.get.d.ts +5 -0
  37. package/build/routes/edge/http/json-list.get.js +5 -0
  38. package/build/routes/edge/http/json-list.get.response.json +52 -0
  39. package/build/routes/edge/http/json-new.put.d.ts +5 -0
  40. package/build/routes/edge/http/json-new.put.js +5 -0
  41. package/build/routes/edge/http/json-new.put.response.json +44 -0
  42. package/build/routes/edge/http/json-protocol.get.d.ts +5 -0
  43. package/build/routes/edge/http/json-protocol.get.js +5 -0
  44. package/build/routes/edge/http/json-protocol.get.response.json +6 -0
  45. package/build/routes/edge/http/json-version.get.d.ts +5 -0
  46. package/build/routes/edge/http/json-version.get.js +5 -0
  47. package/build/routes/edge/http/json-version.get.response.json +44 -0
  48. package/build/routes/edge/http/pdf.post.body.json +724 -0
  49. package/build/routes/edge/http/pdf.post.d.ts +8 -0
  50. package/build/routes/edge/http/pdf.post.js +7 -0
  51. package/build/routes/edge/http/pdf.post.query.json +120 -0
  52. package/build/routes/edge/http/pdf.post.response.json +5 -0
  53. package/build/routes/edge/http/performance.post.body.json +26 -0
  54. package/build/routes/edge/http/performance.post.d.ts +8 -0
  55. package/build/routes/edge/http/performance.post.js +7 -0
  56. package/build/routes/edge/http/performance.post.query.json +120 -0
  57. package/build/routes/edge/http/performance.post.response.json +7 -0
  58. package/build/routes/edge/http/scrape.post.body.json +626 -0
  59. package/build/routes/edge/http/scrape.post.d.ts +8 -0
  60. package/build/routes/edge/http/scrape.post.js +7 -0
  61. package/build/routes/edge/http/scrape.post.query.json +183 -0
  62. package/build/routes/edge/http/scrape.post.response.json +334 -0
  63. package/build/routes/edge/http/screenshot.post.body.json +669 -0
  64. package/build/routes/edge/http/screenshot.post.d.ts +8 -0
  65. package/build/routes/edge/http/screenshot.post.js +7 -0
  66. package/build/routes/edge/http/screenshot.post.query.json +120 -0
  67. package/build/routes/edge/http/screenshot.post.response.json +5 -0
  68. package/build/routes/edge/tests/content.spec.d.ts +1 -0
  69. package/build/routes/edge/tests/content.spec.js +312 -0
  70. package/build/routes/edge/tests/download.spec.d.ts +1 -0
  71. package/build/routes/edge/tests/download.spec.js +67 -0
  72. package/build/routes/edge/tests/function.spec.d.ts +1 -0
  73. package/build/routes/edge/tests/function.spec.js +277 -0
  74. package/build/routes/edge/tests/json-version.spec.d.ts +1 -0
  75. package/build/routes/edge/tests/json-version.spec.js +37 -0
  76. package/build/routes/edge/tests/kill-sessions.spec.d.ts +1 -0
  77. package/build/routes/edge/tests/kill-sessions.spec.js +80 -0
  78. package/build/routes/edge/tests/page-websocket.spec.d.ts +1 -0
  79. package/build/routes/edge/tests/page-websocket.spec.js +97 -0
  80. package/build/routes/edge/tests/pdf.spec.d.ts +1 -0
  81. package/build/routes/edge/tests/pdf.spec.js +345 -0
  82. package/build/routes/edge/tests/performance.spec.d.ts +1 -0
  83. package/build/routes/edge/tests/performance.spec.js +124 -0
  84. package/build/routes/edge/tests/scrape.spec.d.ts +1 -0
  85. package/build/routes/edge/tests/scrape.spec.js +354 -0
  86. package/build/routes/edge/tests/screenshot.spec.d.ts +1 -0
  87. package/build/routes/edge/tests/screenshot.spec.js +339 -0
  88. package/build/routes/edge/tests/websocket.spec.d.ts +1 -0
  89. package/build/routes/edge/tests/websocket.spec.js +384 -0
  90. package/build/routes/edge/ws/browser.d.ts +7 -0
  91. package/build/routes/edge/ws/browser.js +6 -0
  92. package/build/routes/edge/ws/browser.query.json +120 -0
  93. package/build/routes/edge/ws/cdp.d.ts +8 -0
  94. package/build/routes/edge/ws/cdp.js +7 -0
  95. package/build/routes/edge/ws/cdp.query.json +120 -0
  96. package/build/routes/edge/ws/page.d.ts +8 -0
  97. package/build/routes/edge/ws/page.js +7 -0
  98. package/build/routes/edge/ws/page.query.json +120 -0
  99. package/build/routes/edge/ws/playwright.d.ts +8 -0
  100. package/build/routes/edge/ws/playwright.js +7 -0
  101. package/build/routes/edge/ws/playwright.query.json +100 -0
  102. package/build/routes/firefox/tests/kill-sessions.spec.js +1 -1
  103. package/build/routes/webkit/tests/kill-sessions.spec.js +1 -1
  104. package/build/sdk-utils.js +1 -1
  105. package/build/shared/scrape.http.js +2 -2
  106. package/build/types.d.ts +32 -0
  107. package/build/types.js +18 -0
  108. package/build/utils.d.ts +1 -0
  109. package/build/utils.js +16 -2
  110. package/docker/chrome/Dockerfile +14 -14
  111. package/docker/chromium/Dockerfile +14 -14
  112. package/docker/edge/.dockerignore +16 -0
  113. package/docker/edge/Dockerfile +43 -0
  114. package/docker/firefox/Dockerfile +14 -14
  115. package/docker/multi/Dockerfile +18 -18
  116. package/docker/sdk/Dockerfile +10 -0
  117. package/package.json +18 -18
  118. package/src/browserless.ts +4 -0
  119. package/src/browsers/browsers.cdp.ts +5 -0
  120. package/src/browsers/browsers.playwright.ts +6 -0
  121. package/src/browsers/index.ts +4 -1
  122. package/src/http.ts +9 -0
  123. package/src/routes/chrome/tests/kill-sessions.spec.ts +1 -1
  124. package/src/routes/chromium/tests/kill-sessions.spec.ts +1 -1
  125. package/src/routes/edge/http/content.post.ts +20 -0
  126. package/src/routes/edge/http/download.post.ts +20 -0
  127. package/src/routes/edge/http/function.post.ts +20 -0
  128. package/src/routes/edge/http/json-list.get.ts +7 -0
  129. package/src/routes/edge/http/json-new.put.ts +7 -0
  130. package/src/routes/edge/http/json-protocol.get.ts +7 -0
  131. package/src/routes/edge/http/json-version.get.ts +7 -0
  132. package/src/routes/edge/http/pdf.post.ts +20 -0
  133. package/src/routes/edge/http/performance.post.ts +20 -0
  134. package/src/routes/edge/http/scrape.post.ts +20 -0
  135. package/src/routes/edge/http/screenshot.post.ts +20 -0
  136. package/src/routes/edge/tests/content.spec.ts +376 -0
  137. package/src/routes/edge/tests/download.spec.ts +77 -0
  138. package/src/routes/edge/tests/function.spec.ts +317 -0
  139. package/src/routes/edge/tests/json-version.spec.ts +52 -0
  140. package/src/routes/edge/tests/kill-sessions.spec.ts +99 -0
  141. package/src/routes/edge/tests/page-websocket.spec.ts +129 -0
  142. package/src/routes/edge/tests/pdf.spec.ts +389 -0
  143. package/src/routes/edge/tests/performance.spec.ts +155 -0
  144. package/src/routes/edge/tests/scrape.spec.ts +417 -0
  145. package/src/routes/edge/tests/screenshot.spec.ts +387 -0
  146. package/src/routes/edge/tests/websocket.spec.ts +510 -0
  147. package/src/routes/edge/ws/browser.ts +10 -0
  148. package/src/routes/edge/ws/cdp.ts +17 -0
  149. package/src/routes/edge/ws/page.ts +10 -0
  150. package/src/routes/edge/ws/playwright.ts +17 -0
  151. package/src/routes/firefox/tests/kill-sessions.spec.ts +1 -1
  152. package/src/routes/webkit/tests/kill-sessions.spec.ts +1 -1
  153. package/src/sdk-utils.ts +1 -1
  154. package/src/shared/scrape.http.ts +2 -2
  155. package/src/types.ts +19 -0
  156. package/src/utils.ts +38 -16
  157. package/static/docs/swagger.json +2097 -10
  158. package/static/docs/swagger.min.json +2096 -9
  159. package/static/function/client.js +76 -68
  160. package/static/function/index.html +76 -68
@@ -0,0 +1,317 @@
1
+ import { Browserless, Config, Metrics } from '@browserless.io/browserless';
2
+ import { expect } from 'chai';
3
+
4
+ describe('/edge/function API', function () {
5
+ let browserless: Browserless;
6
+
7
+ const start = ({
8
+ config = new Config(),
9
+ metrics = new Metrics(),
10
+ }: { config?: Config; metrics?: Metrics } = {}) => {
11
+ browserless = new Browserless({ config, metrics });
12
+ return browserless.start();
13
+ };
14
+
15
+ afterEach(async () => {
16
+ await browserless.stop();
17
+ });
18
+
19
+ it('runs functions', async () => {
20
+ const config = new Config();
21
+ config.setToken('browserless');
22
+ const metrics = new Metrics();
23
+ await start({ config, metrics });
24
+ const body = {
25
+ code: `export default async function ({ page }) {
26
+ return Promise.resolve({
27
+ data: "ok",
28
+ type: "application/text",
29
+ });
30
+ }`,
31
+ context: {},
32
+ };
33
+
34
+ await fetch('http://localhost:3000/edge/function?token=browserless', {
35
+ body: JSON.stringify(body),
36
+ headers: {
37
+ 'content-type': 'application/json',
38
+ },
39
+ method: 'POST',
40
+ }).then(async (res) => {
41
+ const json = await res.json();
42
+
43
+ expect(json).to.have.property('data');
44
+ expect(json.data).to.equal('ok');
45
+ expect(res.status).to.equal(200);
46
+ });
47
+ });
48
+
49
+ it('runs functions with "context"', async () => {
50
+ const config = new Config();
51
+ config.setToken('browserless');
52
+ const metrics = new Metrics();
53
+ await start({ config, metrics });
54
+ const body = {
55
+ code: `export default async function ({ page, context }) {
56
+ if (!!context.ok) {
57
+ return Promise.resolve({
58
+ data: "ok",
59
+ type: "application/text",
60
+ });
61
+ }
62
+ return Promise.reject(new Error('Bad context!'));
63
+ }`,
64
+ context: {
65
+ ok: true,
66
+ },
67
+ };
68
+
69
+ await fetch('http://localhost:3000/edge/function?token=browserless', {
70
+ body: JSON.stringify(body),
71
+ headers: {
72
+ 'content-type': 'application/json',
73
+ },
74
+ method: 'POST',
75
+ }).then(async (res) => {
76
+ const json = await res.json();
77
+
78
+ expect(json).to.have.property('data');
79
+ expect(json.data).to.equal('ok');
80
+ expect(res.status).to.equal(200);
81
+ });
82
+ });
83
+
84
+ it('runs "application/javascript" functions', async () => {
85
+ const config = new Config();
86
+ config.setToken('browserless');
87
+ const metrics = new Metrics();
88
+ await start({ config, metrics });
89
+ const body = `export default async function ({ page }) {
90
+ return Promise.resolve({
91
+ data: "ok",
92
+ type: "application/text",
93
+ });
94
+ }`;
95
+
96
+ await fetch('http://localhost:3000/edge/function?token=browserless', {
97
+ body,
98
+ headers: { 'Content-Type': 'application/javascript' },
99
+ method: 'POST',
100
+ }).then(async (res) => {
101
+ const json = await res.json();
102
+ expect(json).to.have.property('data');
103
+ expect(json.data).to.equal('ok');
104
+ expect(json.type).to.equal('application/text');
105
+ expect(res.status).to.equal(200);
106
+ });
107
+ });
108
+
109
+ it('runs functions that import libraries', async () => {
110
+ const config = new Config();
111
+ config.setToken('browserless');
112
+ const metrics = new Metrics();
113
+ await start({ config, metrics });
114
+ const body = {
115
+ code: `
116
+ import 'https://code.jquery.com/jquery-3.6.0.min.js';
117
+ export default async function ({ page }) {
118
+ return Promise.resolve({
119
+ data: typeof window.jQuery,
120
+ });
121
+ }`,
122
+ context: {},
123
+ };
124
+
125
+ await fetch('http://localhost:3000/edge/function?token=browserless', {
126
+ body: JSON.stringify(body),
127
+ headers: {
128
+ 'content-type': 'application/json',
129
+ },
130
+ method: 'POST',
131
+ }).then(async (res) => {
132
+ const json = await res.json();
133
+
134
+ expect(json).to.have.property('data');
135
+ expect(json.data).to.equal('function');
136
+ expect(res.status).to.equal(200);
137
+ });
138
+ });
139
+
140
+ it('runs functions with custom return types', async () => {
141
+ const config = new Config();
142
+ config.setToken('browserless');
143
+ const metrics = new Metrics();
144
+ await start({ config, metrics });
145
+ const body = {
146
+ code: `
147
+ export default async function ({ page }) {
148
+ return Promise.resolve({
149
+ data: {
150
+ status: 'ok',
151
+ },
152
+ });
153
+ }`,
154
+ context: {},
155
+ };
156
+
157
+ await fetch('http://localhost:3000/edge/function?token=browserless', {
158
+ body: JSON.stringify(body),
159
+ headers: {
160
+ 'content-type': 'application/json',
161
+ },
162
+ method: 'POST',
163
+ }).then(async (res) => {
164
+ const json = await res.json();
165
+
166
+ expect(res.headers.get('content-type')).to.equal(
167
+ `application/json; charset=UTF-8`,
168
+ );
169
+ expect(json).to.have.property('data');
170
+ expect(res.status).to.equal(200);
171
+ });
172
+ });
173
+
174
+ it('times out requests', async () => {
175
+ const config = new Config();
176
+ config.setToken('browserless');
177
+ const metrics = new Metrics();
178
+ await start({ config, metrics });
179
+ const body = {
180
+ code: `export default async function ({ page }) {
181
+ return Promise.resolve({
182
+ data: "ok",
183
+ type: "application/text",
184
+ });
185
+ }`,
186
+ context: {},
187
+ };
188
+
189
+ await fetch(
190
+ 'http://localhost:3000/edge/function?token=browserless&timeout=10',
191
+ {
192
+ body: JSON.stringify(body),
193
+ headers: {
194
+ 'content-type': 'application/json',
195
+ },
196
+ method: 'POST',
197
+ },
198
+ ).then((res) => {
199
+ expect(res.status).to.equal(408);
200
+ });
201
+ });
202
+
203
+ it('rejects requests with bad content-types', async () => {
204
+ const config = new Config();
205
+ config.setConcurrent(0);
206
+ config.setQueued(0);
207
+ config.setToken('browserless');
208
+ const metrics = new Metrics();
209
+ await start({ config, metrics });
210
+
211
+ const body = {
212
+ code: `export default async function ({ page }) {
213
+ return Promise.resolve({
214
+ data: "ok",
215
+ type: "application/text",
216
+ });
217
+ }`,
218
+ context: {},
219
+ };
220
+
221
+ await fetch('http://localhost:3000/edge/function?token=browserless', {
222
+ body: JSON.stringify(body),
223
+ headers: {
224
+ 'content-type': 'joelson',
225
+ },
226
+ method: 'POST',
227
+ }).then(async (res) => {
228
+ return expect(res.status).to.equal(404);
229
+ });
230
+ });
231
+
232
+ it('rejects requests with 429', async () => {
233
+ const config = new Config();
234
+ config.setConcurrent(0);
235
+ config.setQueued(0);
236
+ config.setToken('browserless');
237
+ const metrics = new Metrics();
238
+ await start({ config, metrics });
239
+
240
+ const body = {
241
+ code: `export default async function ({ page }) {
242
+ return Promise.resolve({
243
+ data: "ok",
244
+ type: "application/text",
245
+ });
246
+ }`,
247
+ context: {},
248
+ };
249
+
250
+ await fetch('http://localhost:3000/edge/function?token=browserless', {
251
+ body: JSON.stringify(body),
252
+ headers: {
253
+ 'content-type': 'application/json',
254
+ },
255
+ method: 'POST',
256
+ }).then(async (res) => {
257
+ return expect(res.status).to.equal(429);
258
+ });
259
+ });
260
+
261
+ it('rejects requests that are unauthorized', async () => {
262
+ const config = new Config();
263
+ config.setToken('browserless');
264
+ const metrics = new Metrics();
265
+ await start({ config, metrics });
266
+
267
+ const body = {
268
+ code: `export default async function ({ page }) {
269
+ return Promise.resolve({
270
+ data: "ok",
271
+ type: "application/text",
272
+ });
273
+ }`,
274
+ context: {},
275
+ };
276
+
277
+ await fetch('http://localhost:3000/edge/function?token=bless', {
278
+ body: JSON.stringify(body),
279
+ headers: {
280
+ 'content-type': 'application/json',
281
+ },
282
+ method: 'POST',
283
+ }).then(async (res) => {
284
+ return expect(res.status).to.equal(401);
285
+ });
286
+ });
287
+
288
+ it('allows requests without token when auth token is not set', async () => {
289
+ const config = new Config();
290
+ const metrics = new Metrics();
291
+ await start({ config, metrics });
292
+
293
+ const body = {
294
+ code: `export default async function ({ page }) {
295
+ return Promise.resolve({
296
+ data: "ok",
297
+ type: "application/text",
298
+ });
299
+ }`,
300
+ context: {},
301
+ };
302
+
303
+ await fetch('http://localhost:3000/edge/function', {
304
+ body: JSON.stringify(body),
305
+ headers: {
306
+ 'content-type': 'application/json',
307
+ },
308
+ method: 'POST',
309
+ }).then(async (res) => {
310
+ const json = await res.json();
311
+
312
+ expect(json).to.have.property('data');
313
+ expect(json.data).to.equal('ok');
314
+ expect(res.status).to.equal(200);
315
+ });
316
+ });
317
+ });
@@ -0,0 +1,52 @@
1
+ import { Browserless, Config, Metrics } from '@browserless.io/browserless';
2
+ import { expect } from 'chai';
3
+
4
+ describe('/json/version API', function () {
5
+ let browserless: Browserless;
6
+
7
+ const start = ({
8
+ config = new Config(),
9
+ metrics = new Metrics(),
10
+ }: { config?: Config; metrics?: Metrics } = {}) => {
11
+ browserless = new Browserless({ config, metrics });
12
+ return browserless.start();
13
+ };
14
+
15
+ afterEach(async () => {
16
+ await browserless.stop();
17
+ });
18
+
19
+ it('allows requests to /json/version', async () => {
20
+ const config = new Config();
21
+ config.setToken('browserless');
22
+ const metrics = new Metrics();
23
+ await start({ config, metrics });
24
+
25
+ const res = await fetch(
26
+ 'http://localhost:3000/json/version?token=browserless',
27
+ );
28
+ const resJSON = await res.json();
29
+
30
+ [
31
+ 'Browser',
32
+ 'Protocol-Version',
33
+ 'User-Agent',
34
+ 'V8-Version',
35
+ 'WebKit-Version',
36
+ 'webSocketDebuggerUrl',
37
+ 'Debugger-Version',
38
+ ].forEach((k) => expect(resJSON).to.haveOwnProperty(k));
39
+ });
40
+
41
+ it('rejects unauthorized requests to /json/version', async () => {
42
+ const config = new Config();
43
+ config.setToken('browserless');
44
+ const metrics = new Metrics();
45
+ await start({ config, metrics });
46
+
47
+ const res = await fetch(
48
+ 'http://localhost:3000/json/version?token=imabadboi',
49
+ );
50
+ expect(res.status).to.equal(401);
51
+ });
52
+ });
@@ -0,0 +1,99 @@
1
+ import { Browserless, Config, Metrics } from '@browserless.io/browserless';
2
+ import { expect } from 'chai';
3
+ import puppeteer from 'puppeteer-core';
4
+
5
+ describe('/kill API', function () {
6
+ let browserless: Browserless;
7
+
8
+ const start = ({
9
+ config = new Config(),
10
+ metrics = new Metrics(),
11
+ }: { config?: Config; metrics?: Metrics } = {}) => {
12
+ config.setToken('6R0W53R135510');
13
+ browserless = new Browserless({ config, metrics });
14
+ return browserless.start();
15
+ };
16
+
17
+ afterEach(async () => {
18
+ await browserless.stop();
19
+ });
20
+
21
+ it('Kill all sessions', async () => {
22
+ await start();
23
+ const browser1 = await puppeteer.connect({
24
+ browserWSEndpoint: `ws://localhost:3000/edge?token=6R0W53R135510`,
25
+ });
26
+ const browser2 = await puppeteer.connect({
27
+ browserWSEndpoint: `ws://localhost:3000/edge?token=6R0W53R135510`,
28
+ });
29
+
30
+ await fetch('http://localhost:3000/kill/all?token=6R0W53R135510').then(
31
+ async (res) => {
32
+ expect(res.status).to.equal(204);
33
+ },
34
+ );
35
+
36
+ let errorThrown1;
37
+ try {
38
+ await browser1.newPage();
39
+ } catch (e) {
40
+ errorThrown1 = e;
41
+ }
42
+ let errorThrown2;
43
+ try {
44
+ await browser2.newPage();
45
+ } catch (e) {
46
+ errorThrown2 = e;
47
+ }
48
+ expect((errorThrown1 as Error).message).contains('closed');
49
+ expect((errorThrown2 as Error).message).contains('closed');
50
+ });
51
+
52
+ it('Kill session by browserId', async () => {
53
+ await start();
54
+ const browser = await puppeteer.connect({
55
+ browserWSEndpoint: `ws://localhost:3000/edge?token=6R0W53R135510`,
56
+ });
57
+
58
+ await fetch('http://localhost:3000/sessions?token=6R0W53R135510').then(
59
+ async (res) => {
60
+ const sessions = await res.json();
61
+ const browserId = sessions[0].browserId;
62
+ await fetch(
63
+ `http://localhost:3000/kill/${browserId}?token=6R0W53R135510`,
64
+ ).then(async (res) => {
65
+ expect(res.status).to.equal(204);
66
+ });
67
+ },
68
+ );
69
+
70
+ let errorThrown;
71
+ try {
72
+ await browser.newPage();
73
+ } catch (e) {
74
+ errorThrown = e;
75
+ }
76
+ expect((errorThrown as Error).message).contains('closed');
77
+ });
78
+
79
+ it('Kill session by trackingId', async () => {
80
+ await start();
81
+ const browser = await puppeteer.connect({
82
+ browserWSEndpoint: `ws://localhost:3000/edge?token=6R0W53R135510&trackingId=session-1`,
83
+ });
84
+
85
+ await fetch(
86
+ 'http://localhost:3000/kill/session-1?token=6R0W53R135510',
87
+ ).then(async (res) => {
88
+ expect(res.status).to.equal(204);
89
+ });
90
+
91
+ let errorThrown;
92
+ try {
93
+ await browser.newPage();
94
+ } catch (e) {
95
+ errorThrown = e;
96
+ }
97
+ expect((errorThrown as Error).message).contains('closed');
98
+ });
99
+ });
@@ -0,0 +1,129 @@
1
+ import { Browserless, Config, Metrics } from '@browserless.io/browserless';
2
+ import puppeteer, { Connection } from 'puppeteer-core';
3
+ import { NodeWebSocketTransport } from 'puppeteer-core/lib/esm/puppeteer/node/NodeWebSocketTransport.js';
4
+ import { expect } from 'chai';
5
+
6
+ describe('WebSocket Page API', function () {
7
+ // Server shutdown can take a few seconds
8
+ // and so can these tests :/
9
+ this.timeout(5000);
10
+
11
+ let browserless: Browserless;
12
+
13
+ const start = ({
14
+ config = new Config(),
15
+ metrics = new Metrics(),
16
+ }: { config?: Config; metrics?: Metrics } = {}) => {
17
+ browserless = new Browserless({ config, metrics });
18
+ return browserless.start();
19
+ };
20
+
21
+ afterEach(async () => {
22
+ await browserless.stop();
23
+ });
24
+
25
+ it('forwards requests to running pages', async () => {
26
+ const config = new Config();
27
+ const metrics = new Metrics();
28
+ await start({ config, metrics });
29
+
30
+ const browser = await puppeteer.connect({
31
+ browserWSEndpoint: `ws://localhost:3000/edge`,
32
+ });
33
+ const page = await browser.newPage();
34
+ await page.goto('https://example.com/');
35
+ // @ts-ignore
36
+ const pageId = page.target()._targetId;
37
+ const webSocketDebuggerUrl = `ws://localhost:3000/devtools/page/${pageId}`;
38
+
39
+ // Connect to raw page target
40
+ const cdp = new Connection(
41
+ webSocketDebuggerUrl,
42
+ await NodeWebSocketTransport.create(webSocketDebuggerUrl),
43
+ );
44
+
45
+ // Send a command
46
+ const result = await cdp.send('Page.enable');
47
+ await browser.close();
48
+ expect(result);
49
+ });
50
+
51
+ it('creates pages when interacting with /json/new', async () => {
52
+ const config = new Config();
53
+ const metrics = new Metrics();
54
+ await start({ config, metrics });
55
+
56
+ const { webSocketDebuggerUrl } = await fetch(
57
+ 'http://localhost:3000/json/new',
58
+ {
59
+ method: 'PUT',
60
+ },
61
+ ).then((r) => r.json());
62
+
63
+ // Connect to raw page target
64
+ const cdp = new Connection(
65
+ webSocketDebuggerUrl,
66
+ await NodeWebSocketTransport.create(webSocketDebuggerUrl),
67
+ );
68
+
69
+ // Send a command
70
+ const result = await cdp.send('Page.enable');
71
+ cdp.dispose();
72
+ expect(result);
73
+ });
74
+
75
+ it('rejects unauthorized page requests', async () => {
76
+ const config = new Config();
77
+ config.setToken('browserless');
78
+ const metrics = new Metrics();
79
+ await start({ config, metrics });
80
+
81
+ const browser = await puppeteer.connect({
82
+ browserWSEndpoint: `ws://localhost:3000/edge?token=browserless`,
83
+ });
84
+ const page = await browser.newPage();
85
+ await page.goto('https://example.com/');
86
+ // @ts-ignore
87
+ const pageId = page.target()._targetId;
88
+ const webSocketDebuggerUrl = `ws://localhost:3000/devtools/page/${pageId}`;
89
+
90
+ // Connect to raw page target without authorization
91
+ try {
92
+ new Connection(
93
+ webSocketDebuggerUrl,
94
+ await NodeWebSocketTransport.create(webSocketDebuggerUrl),
95
+ );
96
+ } catch (err: unknown) {
97
+ //@ts-ignore
98
+ expect(err.message).to.include('401');
99
+ } finally {
100
+ browser.close();
101
+ }
102
+ });
103
+
104
+ it('404s pages not found', async () => {
105
+ const config = new Config();
106
+ const metrics = new Metrics();
107
+ await start({ config, metrics });
108
+
109
+ const browser = await puppeteer.connect({
110
+ browserWSEndpoint: `ws://localhost:3000/edge?token=browserless`,
111
+ });
112
+ const page = await browser.newPage();
113
+ await page.goto('https://example.com/');
114
+ const webSocketDebuggerUrl = `ws://localhost:3000/devtools/page/im-a-banana`;
115
+
116
+ // Connect to raw page target without authorization
117
+ try {
118
+ new Connection(
119
+ webSocketDebuggerUrl,
120
+ await NodeWebSocketTransport.create(webSocketDebuggerUrl),
121
+ );
122
+ } catch (err: unknown) {
123
+ //@ts-ignore
124
+ expect(err.message).to.include('404');
125
+ } finally {
126
+ browser.close();
127
+ }
128
+ });
129
+ });