Skip to content

Commit f1d6218

Browse files
committed
🔧 Added unit and end to end tests using jest
1 parent be3d44b commit f1d6218

File tree

7 files changed

+1066
-21
lines changed

7 files changed

+1066
-21
lines changed

‎bun.lock

Lines changed: 816 additions & 9 deletions
Large diffs are not rendered by default.

‎jest.config.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export default {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
testMatch: [
5+
'**/tests/**/*.test.ts'
6+
],
7+
coverageDirectory: 'coverage',
8+
collectCoverageFrom: [
9+
'src/**/*.ts',
10+
'!src/types/**/*.ts'
11+
],
12+
transform: {
13+
'^.+\\.tsx?$': ['ts-jest', {
14+
tsconfig: 'tsconfig.json'
15+
}]
16+
},
17+
setupFilesAfterEnv: ['./tests/jest.setup.ts'],
18+
testTimeout: 30000,
19+
verbose: true
20+
};

‎package.json

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@
1212
"type": "git",
1313
"url": "git+https://github.com/dark1zinn/puppet-manager.git"
1414
},
15-
"scripts": {
16-
"build": "tsup",
17-
"test": "jest",
18-
"load-env": "node -r dotenv/config -e \"console.log('Environment variables loaded from .env')\"",
19-
"publish:npm": "bun run build && bun run load-env && npm publish --access public",
20-
"publish:github": "bun run build && bun run load-env && npm publish --registry https://npm.pkg.github.com",
21-
"publish:all": "bun run publish:npm && bun run publish:github"
22-
},
23-
"publishConfig": {
24-
"registry": "https://registry.npmjs.org/"
25-
},
15+
"scripts": {
16+
"build": "tsup",
17+
"test": "jest --detectOpenHandles",
18+
"load-env": "node -r dotenv/config -e \"console.log('Environment variables loaded from .env')\"",
19+
"publish:npm": "bun run build && bun run load-env && npm publish --access public",
20+
"publish:github": "bun run build && bun run load-env && npm publish --registry https://npm.pkg.github.com",
21+
"publish:all": "bun run publish:npm && bun run publish:github"
22+
},
23+
"publishConfig": {
24+
"registry": "https://registry.npmjs.org/"
25+
},
2626
"keywords": [
2727
"puppeteer",
2828
"automation",
@@ -39,6 +39,7 @@
3939
"dotenv": "^16.4.7",
4040
"jest": "^29.5.0",
4141
"ts-jest": "^29.1.0",
42+
"ts-node": "^10.9.2",
4243
"tsup": "^8.4.0",
4344
"typescript": "^5.0.0"
4445
},
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { PuppetManager } from '../../src/PuppetManager';
2+
3+
describe('PuppetManager E2E', () => {
4+
let manager: PuppetManager;
5+
6+
beforeEach(() => {
7+
manager = new PuppetManager({
8+
browserOptions: {
9+
headless: true
10+
},
11+
defaultTimeout: 30
12+
});
13+
});
14+
15+
afterEach(async () => {
16+
await manager.cleanup();
17+
});
18+
19+
it('should create browser and navigate to a page', async () => {
20+
const bid = await manager.createBrowser();
21+
const page = await manager.createPage(bid);
22+
23+
await page.goto('https://example.com');
24+
const title = await page.title();
25+
26+
expect(title).toBe('Example Domain');
27+
}, 30000);
28+
29+
it('should handle multiple browser instances', async () => {
30+
const bid1 = await manager.createBrowser();
31+
const bid2 = await manager.createBrowser();
32+
33+
const page1 = await manager.createPage(bid1);
34+
const page2 = await manager.createPage(bid2);
35+
36+
await Promise.all([
37+
page1.goto('https://example.com'),
38+
page2.goto('https://example.org')
39+
]);
40+
41+
const [title1, title2] = await Promise.all([
42+
page1.title(),
43+
page2.title()
44+
]);
45+
46+
expect(title1).toBe('Example Domain');
47+
expect(title2).toBe('Example Domain');
48+
}, 30000);
49+
50+
it('should properly handle timeouts', async () => {
51+
const bid = await manager.createBrowser();
52+
let isTimeout = false;
53+
54+
manager.subscribeTTL(bid, (ttl) => {
55+
// console.log(`TTL: ${ttl}`);
56+
if (ttl === 0) isTimeout = true;
57+
});
58+
59+
manager.refreshTTL(bid, 2); // 2 seconds timeout
60+
61+
await new Promise(resolve => setTimeout(resolve, 3000));
62+
63+
expect(isTimeout).toBe(true);
64+
// await new Promise(resolve => setTimeout(resolve, 1500));
65+
expect(manager.browsers[bid]).toBeNull();
66+
}, 10000);
67+
68+
it('should handle auto-scrolling functionality', async () => {
69+
const bid = await manager.createBrowser();
70+
const page = await manager.createPage(bid);
71+
72+
await page.goto('https://example.com');
73+
await page.evaluate(() => {
74+
document.body.style.height = '2000px';
75+
});
76+
77+
const { autoScrollToBottom } = await import('../../src/utils');
78+
await autoScrollToBottom(page);
79+
80+
const scrollPosition = await page.evaluate(() => window.scrollY);
81+
expect(scrollPosition).toBeGreaterThan(0);
82+
}, 30000);
83+
84+
it('should handle button clicking when visible', async () => {
85+
const bid = await manager.createBrowser();
86+
const page = await manager.createPage(bid);
87+
88+
await page.goto('https://example.com');
89+
await page.evaluate(() => {
90+
const button = document.createElement('button');
91+
button.id = 'test-button';
92+
button.textContent = 'Test Button';
93+
document.body.appendChild(button);
94+
});
95+
96+
const { clickButtonWhenVisible } = await import('../../src/utils');
97+
await clickButtonWhenVisible(page, '#test-button');
98+
99+
// Verify button was clicked (you might need to add specific verification based on your needs)
100+
const buttonExists = await page.$('#test-button') !== null;
101+
expect(buttonExists).toBe(true);
102+
}, 30000);
103+
});

‎tests/jest.setup.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
jest.setTimeout(30000); // 30 seconds global timeout
2+
3+
beforeAll(() => {
4+
// Setup global test environment
5+
console.log('Setting up test environment...');
6+
});
7+
8+
afterAll(() => {
9+
// Cleanup after all tests
10+
console.log('Cleaning up test environment...');
11+
});

‎tests/unit/PuppetManager.test.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { PuppetManager } from '../../src/PuppetManager';
2+
import puppeteer from 'puppeteer';
3+
4+
jest.mock('puppeteer');
5+
6+
describe('PuppetManager', () => {
7+
let manager: PuppetManager;
8+
const mockBrowser = {
9+
close: jest.fn(),
10+
newPage: jest.fn(),
11+
connected: true,
12+
};
13+
const mockPage = {
14+
goto: jest.fn(),
15+
close: jest.fn(),
16+
};
17+
18+
beforeEach(() => {
19+
jest.clearAllMocks();
20+
(puppeteer.launch as jest.Mock).mockResolvedValue(mockBrowser);
21+
mockBrowser.newPage.mockResolvedValue(mockPage);
22+
manager = new PuppetManager();
23+
});
24+
25+
afterEach(async () => {
26+
await manager.cleanup();
27+
});
28+
29+
describe('createBrowser', () => {
30+
it('should create a new browser instance', async () => {
31+
const bid = await manager.createBrowser();
32+
expect(bid).toBe(0);
33+
expect(puppeteer.launch).toHaveBeenCalled();
34+
});
35+
36+
it('should create multiple browser instances with different IDs', async () => {
37+
const bid1 = await manager.createBrowser();
38+
const bid2 = await manager.createBrowser();
39+
expect(bid1).toBe(0);
40+
expect(bid2).toBe(1);
41+
});
42+
});
43+
44+
describe('createPage', () => {
45+
it('should create a new page in existing browser', async () => {
46+
const bid = await manager.createBrowser();
47+
const page = await manager.createPage(bid);
48+
expect(page).toBe(mockPage);
49+
expect(mockBrowser.newPage).toHaveBeenCalled();
50+
});
51+
52+
it('should throw error for invalid browser ID', async () => {
53+
await expect(manager.createPage(999)).rejects.toThrow();
54+
});
55+
});
56+
57+
describe('refreshTTL', () => {
58+
it('should update TTL for browser instance', async () => {
59+
const bid = await manager.createBrowser();
60+
manager.refreshTTL(bid, 120);
61+
expect(manager.timeToLive[bid]).toBe(120);
62+
});
63+
64+
it('should use default TTL when no timeout specified', async () => {
65+
const bid = await manager.createBrowser();
66+
manager.refreshTTL(bid);
67+
expect(manager.timeToLive[bid]).toBe(60);
68+
});
69+
});
70+
71+
describe('destroyBrowser', () => {
72+
it('should close and cleanup browser instance', async () => {
73+
const bid = await manager.createBrowser();
74+
await manager.destroyBrowser(bid);
75+
expect(mockBrowser.close).toHaveBeenCalled();
76+
expect(manager.browsers[bid]).toBeNull();
77+
});
78+
});
79+
80+
describe('cleanup', () => {
81+
it('should close all browser instances', async () => {
82+
await manager.createBrowser();
83+
await manager.createBrowser();
84+
await manager.cleanup();
85+
expect(mockBrowser.close).toHaveBeenCalledTimes(2);
86+
expect(manager.browsers).toHaveLength(0);
87+
});
88+
});
89+
90+
describe('subscribeTTL', () => {
91+
it('should call callback with TTL updates', async () => {
92+
const bid = await manager.createBrowser();
93+
const mockCallback = jest.fn();
94+
const unsubscribe = manager.subscribeTTL(bid, mockCallback);
95+
96+
// Wait for one tick
97+
await new Promise(resolve => setTimeout(resolve, 1100));
98+
99+
expect(mockCallback).toHaveBeenCalled();
100+
unsubscribe();
101+
});
102+
});
103+
});

‎tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// Bundler mode
1212
"moduleResolution": "node",
1313
"allowImportingTsExtensions": true,
14-
"verbatimModuleSyntax": true,
14+
"verbatimModuleSyntax": false,
1515
"noEmit": true,
1616

1717
// Best practices

0 commit comments

Comments
 (0)