@askjo/camoufox-browser 1.0.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.
@@ -0,0 +1,103 @@
1
+ const { startJoBrowser, stopJoBrowser, getServerUrl } = require('../helpers/startJoBrowser');
2
+ const { startTestSite, stopTestSite, getTestSiteUrl } = require('../helpers/testSite');
3
+ const { createClient } = require('../helpers/client');
4
+
5
+ describe('Concurrency', () => {
6
+ let serverUrl;
7
+ let testSiteUrl;
8
+
9
+ beforeAll(async () => {
10
+ const port = await startJoBrowser();
11
+ serverUrl = getServerUrl();
12
+
13
+ const testPort = await startTestSite();
14
+ testSiteUrl = getTestSiteUrl();
15
+ }, 120000);
16
+
17
+ afterAll(async () => {
18
+ await stopTestSite();
19
+ await stopJoBrowser();
20
+ }, 30000);
21
+
22
+ test('concurrent operations on same tab are serialized', async () => {
23
+ const client = createClient(serverUrl);
24
+
25
+ try {
26
+ const { tabId } = await client.createTab(`${testSiteUrl}/pageA`);
27
+
28
+ // Fire multiple operations concurrently on the same tab
29
+ const operations = [
30
+ client.getSnapshot(tabId),
31
+ client.navigate(tabId, `${testSiteUrl}/pageB`),
32
+ client.getSnapshot(tabId),
33
+ ];
34
+
35
+ // All should complete without errors (tab locking serializes them)
36
+ const results = await Promise.all(operations);
37
+
38
+ expect(results.length).toBe(3);
39
+ // Each result should be valid (no crashes)
40
+ results.forEach(r => expect(r).toBeDefined());
41
+ } finally {
42
+ await client.cleanup();
43
+ }
44
+ });
45
+
46
+ test('parallel operations on different tabs work', async () => {
47
+ const client = createClient(serverUrl);
48
+
49
+ try {
50
+ // Create two tabs
51
+ const tab1 = await client.createTab(`${testSiteUrl}/pageA`);
52
+ const tab2 = await client.createTab(`${testSiteUrl}/pageB`);
53
+
54
+ // Run operations on both tabs in parallel
55
+ const [snap1, snap2] = await Promise.all([
56
+ client.getSnapshot(tab1.tabId),
57
+ client.getSnapshot(tab2.tabId),
58
+ ]);
59
+
60
+ // Both should return valid snapshots
61
+ expect(snap1.snapshot).toContain('Page A');
62
+ expect(snap2.snapshot).toContain('Page B');
63
+ } finally {
64
+ await client.cleanup();
65
+ }
66
+ });
67
+
68
+ test('multiple clients can work independently', async () => {
69
+ const client1 = createClient(serverUrl);
70
+ const client2 = createClient(serverUrl);
71
+
72
+ try {
73
+ // Each client creates their own tab
74
+ const [tab1, tab2] = await Promise.all([
75
+ client1.createTab(`${testSiteUrl}/pageA`),
76
+ client2.createTab(`${testSiteUrl}/pageB`),
77
+ ]);
78
+
79
+ // Verify they are independent
80
+ expect(tab1.tabId).not.toBe(tab2.tabId);
81
+ expect(client1.userId).not.toBe(client2.userId);
82
+
83
+ // Both can operate independently
84
+ const [snap1, snap2] = await Promise.all([
85
+ client1.getSnapshot(tab1.tabId),
86
+ client2.getSnapshot(tab2.tabId),
87
+ ]);
88
+
89
+ expect(snap1.snapshot).toContain('Page A');
90
+ expect(snap2.snapshot).toContain('Page B');
91
+
92
+ // Closing one client's session doesn't affect the other
93
+ await client1.closeSession();
94
+
95
+ // Client 2 still works
96
+ const snap2After = await client2.getSnapshot(tab2.tabId);
97
+ expect(snap2After.snapshot).toContain('Page B');
98
+ } finally {
99
+ await client1.cleanup();
100
+ await client2.cleanup();
101
+ }
102
+ });
103
+ });
@@ -0,0 +1,129 @@
1
+ const { startJoBrowser, stopJoBrowser, getServerUrl } = require('../helpers/startJoBrowser');
2
+ const { startTestSite, stopTestSite, getTestSiteUrl } = require('../helpers/testSite');
3
+ const { createClient } = require('../helpers/client');
4
+
5
+ describe('Form Submission', () => {
6
+ let serverUrl;
7
+ let testSiteUrl;
8
+
9
+ beforeAll(async () => {
10
+ const port = await startJoBrowser();
11
+ serverUrl = getServerUrl();
12
+
13
+ const testPort = await startTestSite();
14
+ testSiteUrl = getTestSiteUrl();
15
+ }, 120000);
16
+
17
+ afterAll(async () => {
18
+ await stopTestSite();
19
+ await stopJoBrowser();
20
+ }, 30000);
21
+
22
+ test('fill form fields and submit via button click', async () => {
23
+ const client = createClient(serverUrl);
24
+
25
+ try {
26
+ const { tabId } = await client.createTab(`${testSiteUrl}/form`);
27
+
28
+ // Fill username
29
+ await client.type(tabId, {
30
+ selector: '#username',
31
+ text: 'testuser'
32
+ });
33
+
34
+ // Fill email
35
+ await client.type(tabId, {
36
+ selector: '#email',
37
+ text: 'test@example.com'
38
+ });
39
+
40
+ // Click submit button
41
+ await client.click(tabId, {
42
+ selector: '#submitBtn'
43
+ });
44
+
45
+ // Wait for form submission and navigation
46
+ const snapshot = await client.waitForUrl(tabId, '/submitted');
47
+
48
+ expect(snapshot.url).toContain('/submitted');
49
+ expect(snapshot.snapshot).toContain('Form Submitted Successfully');
50
+ expect(snapshot.snapshot).toContain('Username: testuser');
51
+ expect(snapshot.snapshot).toContain('Email: test@example.com');
52
+ } finally {
53
+ await client.cleanup();
54
+ }
55
+ });
56
+
57
+ test('click button on page', async () => {
58
+ const client = createClient(serverUrl);
59
+
60
+ try {
61
+ const { tabId } = await client.createTab(`${testSiteUrl}/click`);
62
+
63
+ // Initial state
64
+ let snapshot = await client.getSnapshot(tabId);
65
+ expect(snapshot.snapshot).not.toContain('Button was clicked!');
66
+
67
+ // Click the button
68
+ await client.click(tabId, {
69
+ selector: '#clickMe'
70
+ });
71
+
72
+ // Verify click effect
73
+ snapshot = await client.waitForSnapshotContains(tabId, 'Button was clicked!');
74
+ expect(snapshot.snapshot).toContain('Button was clicked!');
75
+ } finally {
76
+ await client.cleanup();
77
+ }
78
+ });
79
+
80
+ test('click using ref', async () => {
81
+ const client = createClient(serverUrl);
82
+
83
+ try {
84
+ const { tabId } = await client.createTab(`${testSiteUrl}/click`);
85
+
86
+ // Get snapshot to find button ref
87
+ const snapshot = await client.getSnapshot(tabId);
88
+
89
+ // Find ref for "Click Me" button
90
+ const match = snapshot.snapshot.match(/\[(e\d+)\].*button.*Click Me/i);
91
+
92
+ if (match) {
93
+ const ref = match[1];
94
+ await client.click(tabId, { ref });
95
+
96
+ const updatedSnapshot = await client.waitForSnapshotContains(tabId, 'Button was clicked!');
97
+ expect(updatedSnapshot.snapshot).toContain('Button was clicked!');
98
+ } else {
99
+ // Fallback to selector
100
+ await client.click(tabId, { selector: '#clickMe' });
101
+ const updatedSnapshot = await client.waitForSnapshotContains(tabId, 'Button was clicked!');
102
+ expect(updatedSnapshot.snapshot).toContain('Button was clicked!');
103
+ }
104
+ } finally {
105
+ await client.cleanup();
106
+ }
107
+ });
108
+
109
+ test('click link to navigate', async () => {
110
+ const client = createClient(serverUrl);
111
+
112
+ try {
113
+ const { tabId } = await client.createTab(`${testSiteUrl}/pageA`);
114
+
115
+ // Click the link to page B
116
+ await client.click(tabId, {
117
+ selector: 'a[href="/pageB"]'
118
+ });
119
+
120
+ // Wait for navigation
121
+ const snapshot = await client.waitForUrl(tabId, '/pageB');
122
+
123
+ expect(snapshot.url).toContain('/pageB');
124
+ expect(snapshot.snapshot).toContain('Page B');
125
+ } finally {
126
+ await client.cleanup();
127
+ }
128
+ });
129
+ });
@@ -0,0 +1,92 @@
1
+ const { startJoBrowser, stopJoBrowser, getServerUrl } = require('../helpers/startJoBrowser');
2
+ const { startTestSite, stopTestSite, getTestSiteUrl } = require('../helpers/testSite');
3
+ const { createClient } = require('../helpers/client');
4
+
5
+ describe('Macro Navigation', () => {
6
+ let serverUrl;
7
+ let testSiteUrl;
8
+
9
+ beforeAll(async () => {
10
+ const port = await startJoBrowser();
11
+ serverUrl = getServerUrl();
12
+
13
+ const testPort = await startTestSite();
14
+ testSiteUrl = getTestSiteUrl();
15
+ }, 120000);
16
+
17
+ afterAll(async () => {
18
+ await stopTestSite();
19
+ await stopJoBrowser();
20
+ }, 30000);
21
+
22
+ test('unknown macro returns error when no fallback URL', async () => {
23
+ const client = createClient(serverUrl);
24
+
25
+ try {
26
+ const { tabId } = await client.createTab();
27
+
28
+ await expect(client.navigate(tabId, '@nonexistent_macro test query'))
29
+ .rejects.toThrow(/url or macro required/);
30
+ } finally {
31
+ await client.cleanup();
32
+ }
33
+ });
34
+
35
+ test('client parses @macro syntax correctly', async () => {
36
+ const client = createClient(serverUrl);
37
+
38
+ try {
39
+ const { tabId } = await client.createTab();
40
+
41
+ // Navigate to a real URL first so we have a valid tab
42
+ await client.navigate(tabId, `${testSiteUrl}/pageA`);
43
+
44
+ // Now try an unknown macro - if client parsing works,
45
+ // server will receive {macro: "@unknown", query: "with spaces"}
46
+ // and return "url or macro required" error
47
+ await expect(client.navigate(tabId, '@unknown with spaces'))
48
+ .rejects.toThrow(/url or macro required/);
49
+ } finally {
50
+ await client.cleanup();
51
+ }
52
+ });
53
+
54
+ test('regular URL still works after macro changes', async () => {
55
+ const client = createClient(serverUrl);
56
+
57
+ try {
58
+ const { tabId } = await client.createTab();
59
+
60
+ // Regular URL should still work
61
+ const result = await client.navigate(tabId, `${testSiteUrl}/pageA`);
62
+
63
+ expect(result.ok).toBe(true);
64
+ expect(result.url).toContain('/pageA');
65
+
66
+ const snapshot = await client.getSnapshot(tabId);
67
+ expect(snapshot.snapshot).toContain('Page A');
68
+ } finally {
69
+ await client.cleanup();
70
+ }
71
+ });
72
+
73
+ test('navigate API accepts macro and query params directly', async () => {
74
+ const client = createClient(serverUrl);
75
+
76
+ try {
77
+ const { tabId } = await client.createTab();
78
+
79
+ // Test the raw API with macro param directly (bypass client parsing)
80
+ // Unknown macro should fail
81
+ await expect(
82
+ client.request('POST', `/tabs/${tabId}/navigate`, {
83
+ userId: client.userId,
84
+ macro: '@fake_macro',
85
+ query: 'test'
86
+ })
87
+ ).rejects.toThrow(/url or macro required/);
88
+ } finally {
89
+ await client.cleanup();
90
+ }
91
+ });
92
+ });
@@ -0,0 +1,128 @@
1
+ const { startJoBrowser, stopJoBrowser, getServerUrl } = require('../helpers/startJoBrowser');
2
+ const { startTestSite, stopTestSite, getTestSiteUrl } = require('../helpers/testSite');
3
+ const { createClient } = require('../helpers/client');
4
+
5
+ describe('Navigation', () => {
6
+ let serverUrl;
7
+ let testSiteUrl;
8
+
9
+ beforeAll(async () => {
10
+ const port = await startJoBrowser();
11
+ serverUrl = getServerUrl();
12
+
13
+ const testPort = await startTestSite();
14
+ testSiteUrl = getTestSiteUrl();
15
+ }, 120000);
16
+
17
+ afterAll(async () => {
18
+ await stopTestSite();
19
+ await stopJoBrowser();
20
+ }, 30000);
21
+
22
+ test('navigate to URL', async () => {
23
+ const client = createClient(serverUrl);
24
+
25
+ try {
26
+ const { tabId } = await client.createTab();
27
+
28
+ const result = await client.navigate(tabId, `${testSiteUrl}/pageA`);
29
+
30
+ expect(result.ok).toBe(true);
31
+ expect(result.url).toContain('/pageA');
32
+
33
+ const snapshot = await client.getSnapshot(tabId);
34
+ expect(snapshot.snapshot).toContain('Welcome to Page A');
35
+ } finally {
36
+ await client.cleanup();
37
+ }
38
+ });
39
+
40
+ test('navigate back', async () => {
41
+ const client = createClient(serverUrl);
42
+
43
+ try {
44
+ const { tabId } = await client.createTab(`${testSiteUrl}/pageA`);
45
+ await client.navigate(tabId, `${testSiteUrl}/pageB`);
46
+
47
+ // Verify we're on page B
48
+ let snapshot = await client.getSnapshot(tabId);
49
+ expect(snapshot.snapshot).toContain('Page B');
50
+
51
+ // Go back
52
+ const result = await client.back(tabId);
53
+ expect(result.ok).toBe(true);
54
+ expect(result.url).toContain('/pageA');
55
+
56
+ snapshot = await client.getSnapshot(tabId);
57
+ expect(snapshot.snapshot).toContain('Page A');
58
+ } finally {
59
+ await client.cleanup();
60
+ }
61
+ });
62
+
63
+ test('navigate forward', async () => {
64
+ const client = createClient(serverUrl);
65
+
66
+ try {
67
+ const { tabId } = await client.createTab(`${testSiteUrl}/pageA`);
68
+ await client.navigate(tabId, `${testSiteUrl}/pageB`);
69
+ await client.back(tabId);
70
+
71
+ // Verify we're back on page A
72
+ let snapshot = await client.getSnapshot(tabId);
73
+ expect(snapshot.snapshot).toContain('Page A');
74
+
75
+ // Go forward
76
+ const result = await client.forward(tabId);
77
+ expect(result.ok).toBe(true);
78
+ expect(result.url).toContain('/pageB');
79
+
80
+ snapshot = await client.getSnapshot(tabId);
81
+ expect(snapshot.snapshot).toContain('Page B');
82
+ } finally {
83
+ await client.cleanup();
84
+ }
85
+ });
86
+
87
+ test('refresh page', async () => {
88
+ const client = createClient(serverUrl);
89
+
90
+ try {
91
+ // Use the refresh counter page
92
+ const { tabId } = await client.createTab(`${testSiteUrl}/refresh-test`);
93
+
94
+ let snapshot = await client.getSnapshot(tabId);
95
+ const initialMatch = snapshot.snapshot.match(/Count: (\d+)/);
96
+ const initialCount = initialMatch ? parseInt(initialMatch[1]) : 0;
97
+
98
+ // Refresh the page
99
+ const result = await client.refresh(tabId);
100
+ expect(result.ok).toBe(true);
101
+
102
+ snapshot = await client.getSnapshot(tabId);
103
+ const newMatch = snapshot.snapshot.match(/Count: (\d+)/);
104
+ const newCount = newMatch ? parseInt(newMatch[1]) : 0;
105
+
106
+ // Count should have incremented
107
+ expect(newCount).toBe(initialCount + 1);
108
+ } finally {
109
+ await client.cleanup();
110
+ }
111
+ });
112
+
113
+ test('navigation updates visited URLs', async () => {
114
+ const client = createClient(serverUrl);
115
+
116
+ try {
117
+ const { tabId } = await client.createTab(`${testSiteUrl}/pageA`);
118
+ await client.navigate(tabId, `${testSiteUrl}/pageB`);
119
+
120
+ const stats = await client.getStats(tabId);
121
+
122
+ expect(stats.visitedUrls).toContain(`${testSiteUrl}/pageA`);
123
+ expect(stats.visitedUrls).toContain(`${testSiteUrl}/pageB`);
124
+ } finally {
125
+ await client.cleanup();
126
+ }
127
+ });
128
+ });
@@ -0,0 +1,81 @@
1
+ const { startJoBrowser, stopJoBrowser, getServerUrl } = require('../helpers/startJoBrowser');
2
+ const { startTestSite, stopTestSite, getTestSiteUrl } = require('../helpers/testSite');
3
+ const { createClient } = require('../helpers/client');
4
+
5
+ describe('Scroll', () => {
6
+ let serverUrl;
7
+ let testSiteUrl;
8
+
9
+ beforeAll(async () => {
10
+ const port = await startJoBrowser();
11
+ serverUrl = getServerUrl();
12
+
13
+ const testPort = await startTestSite();
14
+ testSiteUrl = getTestSiteUrl();
15
+ }, 120000);
16
+
17
+ afterAll(async () => {
18
+ await stopTestSite();
19
+ await stopJoBrowser();
20
+ }, 30000);
21
+
22
+ test('scroll down page', async () => {
23
+ const client = createClient(serverUrl);
24
+
25
+ try {
26
+ const { tabId } = await client.createTab(`${testSiteUrl}/scroll`);
27
+
28
+ // Scroll down
29
+ const result = await client.scroll(tabId, {
30
+ direction: 'down',
31
+ amount: 500
32
+ });
33
+
34
+ expect(result.ok).toBe(true);
35
+ } finally {
36
+ await client.cleanup();
37
+ }
38
+ });
39
+
40
+ test('scroll to bottom of page', async () => {
41
+ const client = createClient(serverUrl);
42
+
43
+ try {
44
+ const { tabId } = await client.createTab(`${testSiteUrl}/scroll`);
45
+
46
+ // Scroll to bottom
47
+ const result = await client.scroll(tabId, {
48
+ direction: 'down',
49
+ amount: 10000 // Large number to reach bottom
50
+ });
51
+
52
+ expect(result.ok).toBe(true);
53
+
54
+ // The snapshot might now include "Bottom of page" text
55
+ // (depending on viewport and scroll behavior)
56
+ } finally {
57
+ await client.cleanup();
58
+ }
59
+ });
60
+
61
+ test('scroll up page', async () => {
62
+ const client = createClient(serverUrl);
63
+
64
+ try {
65
+ const { tabId } = await client.createTab(`${testSiteUrl}/scroll`);
66
+
67
+ // First scroll down
68
+ await client.scroll(tabId, { direction: 'down', amount: 1000 });
69
+
70
+ // Then scroll up
71
+ const result = await client.scroll(tabId, {
72
+ direction: 'up',
73
+ amount: 500
74
+ });
75
+
76
+ expect(result.ok).toBe(true);
77
+ } finally {
78
+ await client.cleanup();
79
+ }
80
+ });
81
+ });
@@ -0,0 +1,141 @@
1
+ const { startJoBrowser, stopJoBrowser, getServerUrl } = require('../helpers/startJoBrowser');
2
+ const { startTestSite, stopTestSite, getTestSiteUrl } = require('../helpers/testSite');
3
+ const { createClient } = require('../helpers/client');
4
+
5
+ describe('Snapshot and Links', () => {
6
+ let serverUrl;
7
+ let testSiteUrl;
8
+
9
+ beforeAll(async () => {
10
+ const port = await startJoBrowser();
11
+ serverUrl = getServerUrl();
12
+
13
+ const testPort = await startTestSite();
14
+ testSiteUrl = getTestSiteUrl();
15
+ }, 120000);
16
+
17
+ afterAll(async () => {
18
+ await stopTestSite();
19
+ await stopJoBrowser();
20
+ }, 30000);
21
+
22
+ test('get snapshot returns page content', async () => {
23
+ const client = createClient(serverUrl);
24
+
25
+ try {
26
+ const { tabId } = await client.createTab(`${testSiteUrl}/pageA`);
27
+
28
+ const snapshot = await client.getSnapshot(tabId);
29
+
30
+ expect(snapshot.url).toContain('/pageA');
31
+ expect(snapshot.snapshot).toBeDefined();
32
+ expect(typeof snapshot.snapshot).toBe('string');
33
+ expect(snapshot.snapshot).toContain('Page A');
34
+ expect(snapshot.refsCount).toBeDefined();
35
+ } finally {
36
+ await client.cleanup();
37
+ }
38
+ });
39
+
40
+ test('snapshot contains element refs', async () => {
41
+ const client = createClient(serverUrl);
42
+
43
+ try {
44
+ const { tabId } = await client.createTab(`${testSiteUrl}/links`);
45
+
46
+ const snapshot = await client.getSnapshot(tabId);
47
+
48
+ // Snapshot should contain ref markers like [e1], [e2], etc.
49
+ expect(snapshot.snapshot).toMatch(/\[e\d+\]/);
50
+ } finally {
51
+ await client.cleanup();
52
+ }
53
+ });
54
+
55
+ test('get links returns all links on page', async () => {
56
+ const client = createClient(serverUrl);
57
+
58
+ try {
59
+ const { tabId } = await client.createTab(`${testSiteUrl}/links`);
60
+
61
+ const result = await client.getLinks(tabId);
62
+
63
+ expect(result.links).toBeDefined();
64
+ expect(Array.isArray(result.links)).toBe(true);
65
+ expect(result.links.length).toBe(5);
66
+
67
+ // Check link structure
68
+ const link = result.links[0];
69
+ expect(link.url).toContain('example.com');
70
+ expect(link.text).toBeDefined();
71
+
72
+ // Check pagination info
73
+ expect(result.pagination).toBeDefined();
74
+ expect(result.pagination.total).toBe(5);
75
+ } finally {
76
+ await client.cleanup();
77
+ }
78
+ });
79
+
80
+ test('get links supports pagination', async () => {
81
+ const client = createClient(serverUrl);
82
+
83
+ try {
84
+ const { tabId } = await client.createTab(`${testSiteUrl}/links`);
85
+
86
+ // Get first 2 links
87
+ const page1 = await client.getLinks(tabId, { limit: 2, offset: 0 });
88
+ expect(page1.links.length).toBe(2);
89
+ expect(page1.pagination.hasMore).toBe(true);
90
+
91
+ // Get next 2 links
92
+ const page2 = await client.getLinks(tabId, { limit: 2, offset: 2 });
93
+ expect(page2.links.length).toBe(2);
94
+ expect(page2.pagination.hasMore).toBe(true);
95
+
96
+ // Get last link
97
+ const page3 = await client.getLinks(tabId, { limit: 2, offset: 4 });
98
+ expect(page3.links.length).toBe(1);
99
+ expect(page3.pagination.hasMore).toBe(false);
100
+
101
+ // Links should be different
102
+ expect(page1.links[0].url).not.toBe(page2.links[0].url);
103
+ } finally {
104
+ await client.cleanup();
105
+ }
106
+ });
107
+
108
+ test('screenshot returns PNG buffer', async () => {
109
+ const client = createClient(serverUrl);
110
+
111
+ try {
112
+ const { tabId } = await client.createTab(`${testSiteUrl}/pageA`);
113
+
114
+ const buffer = await client.screenshot(tabId);
115
+
116
+ expect(buffer).toBeDefined();
117
+ expect(buffer.byteLength).toBeGreaterThan(0);
118
+
119
+ // Check PNG magic bytes
120
+ const bytes = new Uint8Array(buffer);
121
+ expect(bytes[0]).toBe(0x89);
122
+ expect(bytes[1]).toBe(0x50); // P
123
+ expect(bytes[2]).toBe(0x4E); // N
124
+ expect(bytes[3]).toBe(0x47); // G
125
+ } finally {
126
+ await client.cleanup();
127
+ }
128
+ });
129
+
130
+ test('snapshot for non-existent tab returns 404', async () => {
131
+ const client = createClient(serverUrl);
132
+
133
+ try {
134
+ await expect(client.getSnapshot('non-existent-tab-id')).rejects.toMatchObject({
135
+ status: 404
136
+ });
137
+ } finally {
138
+ await client.cleanup();
139
+ }
140
+ });
141
+ });