Skip to content

Commit dc9e175

Browse files
authored
Feat: Support for e2e test using Playwright (#41)
* feat: support for e2e test using Playwright * fix: new splash query issue fixed * fix: better test and optimised * fix: added this as pre push * patch: pausing tests after push * fix: preventing new tabs test from failing * patch: only manual trigger
1 parent e618eb1 commit dc9e175

13 files changed

Lines changed: 544 additions & 14 deletions

File tree

.github/workflows/tests.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: E2E Tests
2+
3+
on:
4+
workflow_dispatch:
5+
6+
concurrency:
7+
group: e2e-${{ github.workflow }}-${{ github.ref }}
8+
cancel-in-progress: true
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
timeout-minutes: 10
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
18+
- name: Setup Node
19+
uses: actions/setup-node@v4
20+
with:
21+
node-version: 20
22+
cache: npm
23+
24+
- name: Install dependencies (clean)
25+
run: npm ci
26+
27+
- name: Install Playwright (Chromium only + system deps)
28+
run: npx playwright install --with-deps chromium
29+
30+
- name: Run tests (headed via XVFB)
31+
env:
32+
DEBUG: pw:api
33+
run: xvfb-run --auto-servernum -- npm test
34+
35+
- name: Upload Playwright HTML report
36+
if: always()
37+
uses: actions/upload-artifact@v4
38+
with:
39+
name: playwright-report
40+
path: playwright-report
41+
retention-days: 7
42+
if-no-files-found: ignore
43+
44+
- name: Upload test results and traces
45+
if: always()
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: test-results
49+
path: test-results
50+
retention-days: 7
51+
if-no-files-found: ignore

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,9 @@ secret.js
6565
backup/*
6666

6767
*.zip
68+
69+
# Playwright test artifacts
70+
playwright-report/
71+
test-results/
72+
# (optional if you enable blob reporter)
73+
blob-report/

.husky/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
npm test

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,32 @@ GoTo is available on [Chrome Web Store](https://chrome.google.com/webstore/detai
126126
1. Clone this repo
127127
```git clone https://github.com/tusharv/GoTo```
128128

129+
## Testing (E2E)
130+
131+
This repo includes Playwright end-to-end tests that load the extension and verify key flows.
132+
133+
Prereqs: Node 18+ (or 20+) installed.
134+
135+
Install and run:
136+
137+
```bash
138+
npm install
139+
npm run test:install
140+
npm test
141+
```
142+
143+
Useful variants:
144+
145+
```bash
146+
# Headed (visible browser)
147+
npm run test:headed
148+
149+
# Playwright UI mode
150+
npm run test:ui
151+
```
152+
153+
CI runs these in GitHub Actions via `xvfb-run` to support headed Chromium.
154+
129155
2. Add `secret.js` in your `src/js`
130156
```JavaScript
131157
var secret = {

package-lock.json

Lines changed: 93 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
"description": "GoTo and Search popular sites from your NewTab",
55
"main": "src/js/background.js",
66
"scripts": {
7-
"test": "echo \"Error: no test specified\" && exit 1"
7+
"test": "playwright test",
8+
"test:ui": "playwright test --ui",
9+
"test:headed": "playwright test --headed",
10+
"test:install": "playwright install chromium",
11+
"prepare": "husky"
812
},
913
"repository": {
1014
"type": "git",
@@ -21,5 +25,9 @@
2125
"bugs": {
2226
"url": "https://github.com/tusharv/GoTo/issues"
2327
},
24-
"homepage": "https://github.com/tusharv/GoTo#readme"
28+
"homepage": "https://github.com/tusharv/GoTo#readme",
29+
"devDependencies": {
30+
"@playwright/test": "^1.47.2",
31+
"husky": "^9.0.11"
32+
}
2533
}

playwright.config.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// @ts-check
2+
const { defineConfig } = require('@playwright/test');
3+
4+
module.exports = defineConfig({
5+
testDir: 'tests/e2e',
6+
timeout: 60000,
7+
fullyParallel: false,
8+
retries: 0,
9+
reporter: [
10+
['github'],
11+
['html', { outputFolder: 'playwright-report', open: 'never' }]
12+
],
13+
outputDir: 'test-results',
14+
use: {
15+
headless: false, // Extensions require headed mode
16+
viewport: { width: 1280, height: 800 },
17+
ignoreHTTPSErrors: true,
18+
video: 'retain-on-failure',
19+
screenshot: 'only-on-failure',
20+
trace: 'retain-on-failure'
21+
},
22+
});
23+
24+

src/js/options.js

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,26 @@ let config = {};
33

44
// Load config from JSON file and initialize UI
55
(async () => {
6-
try {
7-
const response = await fetch(chrome.runtime.getURL('config.json'));
8-
config = await response.json();
9-
// Initialize UI after config is loaded
10-
if (typeof init === 'function') {
11-
init();
12-
}
13-
} catch (error) {
14-
console.error('Error loading config:', error);
15-
// Keep empty config as fallback
16-
}
6+
try {
7+
const response = await fetch(chrome.runtime.getURL('config.json'));
8+
config = await response.json();
9+
// Initialize UI after config is loaded and DOM is ready
10+
const startInit = () => { if (typeof init === 'function') init(); };
11+
if (document.readyState === 'loading') {
12+
document.addEventListener('DOMContentLoaded', startInit, { once: true });
13+
} else {
14+
startInit();
15+
}
16+
} catch (error) {
17+
console.error('Error loading config:', error);
18+
// Keep empty config as fallback
19+
const startInit = () => { if (typeof init === 'function') init(); };
20+
if (document.readyState === 'loading') {
21+
document.addEventListener('DOMContentLoaded', startInit, { once: true });
22+
} else {
23+
startInit();
24+
}
25+
}
1726
})();
1827

1928
let tableContainner;

src/manifest.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
"permissions": [
1616
"storage"
1717
],
18+
"host_permissions": [
19+
"https://api.unsplash.com/*"
20+
],
1821
"action": {
1922
"default_icon": {
2023
"16": "image/icon16.png",
@@ -30,7 +33,7 @@
3033
"128": "image/icon128.png"
3134
},
3235
"content_security_policy": {
33-
"extension_pages": "script-src 'self'; object-src 'self'"
36+
"extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' https://api.unsplash.com"
3437
},
3538
"web_accessible_resources": [
3639
{

tests/e2e/background.spec.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const path = require('path');
2+
const { test, expect } = require('@playwright/test');
3+
const { launchContextWithExtension } = require('./helpers/extension');
4+
5+
test.describe('Background messaging', () => {
6+
let context;
7+
let extensionId;
8+
let page;
9+
10+
test.beforeAll(async () => {
11+
const root = path.resolve(__dirname, '../../');
12+
const launched = await launchContextWithExtension(root);
13+
context = launched.context;
14+
extensionId = launched.extensionId;
15+
page = await context.newPage();
16+
});
17+
18+
test.afterAll(async () => {
19+
await context.close();
20+
});
21+
22+
test('responds to testConfig message', async () => {
23+
await page.goto(`chrome-extension://${extensionId}/page/options.html`);
24+
const result = await page.evaluate(() => new Promise(resolve => {
25+
chrome.runtime.sendMessage({ action: 'testConfig' }, res => resolve(res));
26+
}));
27+
expect(result).toBeTruthy();
28+
expect(result.success).toBe(true);
29+
expect(result.configKeys).toContain('git');
30+
});
31+
32+
test('handles refreshConfig message', async () => {
33+
await page.goto(`chrome-extension://${extensionId}/page/options.html`);
34+
const result = await page.evaluate(() => new Promise(resolve => {
35+
chrome.runtime.sendMessage({ action: 'refreshConfig' }, res => resolve(res));
36+
}));
37+
// Background returns { success: true } for refreshConfig
38+
expect(result).toBeTruthy();
39+
expect(result.success).toBe(true);
40+
});
41+
});
42+
43+

0 commit comments

Comments
 (0)