Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

# testing
/coverage
/test-results
/playwright-report
/blob-report

# production
/build
Expand Down
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
"build": "cross-env NODE_ENV='production' BABEL_ENV='production' node utils/build.js",
"start": "cross-env NODE_ENV='development' BABEL_ENV='development' node utils/server.js",
"deploy": "node scripts/deploy.js",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
Comment thread
wxharry marked this conversation as resolved.
"test:e2e": "playwright test",
"test:e2e:smoke": "playwright test tests/e2e/extension-smoke.spec.ts",
"test:e2e:headed": "playwright test --headed",
"prettier": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,scss}\"",
"prettier:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,scss}\"",
"pretty-quick:check": "pretty-quick --staged --check --pattern \"**/*.{js,jsx,ts,tsx,json,css,scss}\"",
Expand Down Expand Up @@ -64,6 +70,7 @@
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@playwright/test": "^1.53.2",
"@plasmohq/edge-addons-api": "2.0.0",
"@types/color": "^3.0.6",
"@types/firefox-webext-browser": "^94.0.1",
Expand Down Expand Up @@ -98,6 +105,8 @@
"ts-loader": "^9.2.6",
"type-fest": "^3.3.0",
"typescript": "^5",
"jsdom": "^24.1.3",
"vitest": "^2.1.9",
"webpack": "^5.104.1",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^5.2.1"
Expand Down
14 changes: 14 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from '@playwright/test';

export default defineConfig({
testDir: './tests/e2e',
timeout: 45_000,
expect: {
timeout: 10_000,
},
workers: 1,
reporter: 'list',
use: {
trace: 'on-first-retry',
},
});
5 changes: 3 additions & 2 deletions src/pages/Options/index.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
body {
font-family: -apple-system, 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif, 'Helvetica Neue', Helvetica, Arial,
'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
font-family:
-apple-system, 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif, 'Helvetica Neue', Helvetica, Arial, 'PingFang SC',
'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
margin: 0px;
display: flex;
flex-direction: column;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { expect, test, type BrowserContext } from '@playwright/test';
import { cleanupExtensionContext, launchExtensionContext } from './helpers';

test.describe('content script component: developer activity openrank trends', () => {
let context: BrowserContext;
let userDataDir: string;
const developerName = 'octocat';

test.beforeAll(async () => {
const launched = await launchExtensionContext('hypercrx-e2e-content-dev-activity-openrank-');
context = launched.context;
userDataDir = launched.userDataDir;

await context.route(`https://oss.open-digger.cn/github/${developerName}/meta.json`, async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
type: 'user',
updatedAt: 1706745600000,
repos: [],
}),
});
});

await context.route(`https://oss.open-digger.cn/github/${developerName}/activity.json`, async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
'2024-01': 123.45,
'2024-02': 234.56,
'2024-03': 345.67,
}),
});
});

await context.route(`https://oss.open-digger.cn/github/${developerName}/openrank.json`, async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
'2024-01': 11.11,
'2024-02': 22.22,
'2024-03': 33.33,
}),
});
});
});

test.afterAll(async () => {
await cleanupExtensionContext(context, userDataDir);
});

test('renders activity and openrank trend container on a GitHub profile page', async () => {
const page = await context.newPage();
await page.goto(`https://github.com/${developerName}`, { waitUntil: 'domcontentloaded' });

const featureRoot = page.locator('#hypercrx-developer-activity-openrank-trends');
await expect(featureRoot).toHaveCount(1);
await expect(featureRoot.locator('h2.h4.mb-3')).toContainText('Activity & OpenRank Trends');
});

test('renders chart canvas under developer activity openrank section', async () => {
const page = await context.newPage();
await page.goto(`https://github.com/${developerName}`, { waitUntil: 'domcontentloaded' });

const featureRoot = page.locator('#hypercrx-developer-activity-openrank-trends');
await expect(featureRoot).toHaveCount(1);
await expect(featureRoot.locator('canvas')).toHaveCount(1);
});

test('does not render developer activity openrank section on non-github pages', async () => {
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(1000);

await expect(page.locator('#hypercrx-developer-activity-openrank-trends')).toHaveCount(0);
});
});
109 changes: 109 additions & 0 deletions tests/e2e/content-scripts/developer-hovercard-info.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { expect, test, type BrowserContext, type Page } from '@playwright/test';
import { cleanupExtensionContext, gotoPublicRepo, launchExtensionContext } from './helpers';

test.describe('content script component: developer hovercard info', () => {
let context: BrowserContext;
let userDataDir: string;
const developerName = 'octocat';

const mountSyntheticHovercardFixture = async (page: Page) => {
await page.evaluate((name) => {
const existingAnchor = document.getElementById('hypercrx-test-hover-anchor');
if (!existingAnchor) {
const wrapper = document.createElement('div');
wrapper.id = 'hypercrx-test-hover-wrapper';
wrapper.style.position = 'fixed';
wrapper.style.top = '12px';
wrapper.style.left = '12px';
wrapper.style.zIndex = '2147483647';

const anchor = document.createElement('a');
anchor.id = 'hypercrx-test-hover-anchor';
anchor.href = `https://github.com/${name}`;
anchor.textContent = name;
anchor.setAttribute('data-hovercard-url', `/users/${name}/hovercard`);
wrapper.appendChild(anchor);
document.body.appendChild(wrapper);
}

let pageRoot = document.querySelector(
'body > div.logged-in.env-production.page-responsive'
) as HTMLElement | null;
if (!pageRoot) {
pageRoot = document.createElement('div');
pageRoot.className = 'logged-in env-production page-responsive';
document.body.appendChild(pageRoot);
}

let popoverRoot = pageRoot.querySelector(
'div.Popover.js-hovercard-content.position-absolute'
) as HTMLElement | null;
if (!popoverRoot) {
popoverRoot = document.createElement('div');
popoverRoot.className = 'Popover js-hovercard-content position-absolute';
pageRoot.appendChild(popoverRoot);
}

popoverRoot.setAttribute('data-hovercard-target-url', `/users/${name}`);

if (!popoverRoot.querySelector('div.Popover-message')) {
const level1 = document.createElement('div');
const level2 = document.createElement('div');
const level3 = document.createElement('div');
level3.className = 'Popover-message';
level2.appendChild(level3);
level1.appendChild(level2);
popoverRoot.appendChild(level1);
}
}, developerName);
};

test.beforeAll(async () => {
const launched = await launchExtensionContext('hypercrx-e2e-content-hovercard-');
context = launched.context;
userDataDir = launched.userDataDir;

await context.route(`https://oss.open-digger.cn/github/${developerName}/openrank.json`, async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
'2026-01': '123.45',
'2026-02': '234.56',
}),
});
});
});

test.afterAll(async () => {
await cleanupExtensionContext(context, userDataDir);
});

test('injects OpenRank info into GitHub developer hovercard', async () => {
const page = await context.newPage();
await gotoPublicRepo(page);
await page.waitForTimeout(2500);
await mountSyntheticHovercardFixture(page);

const hoverAnchor = page.locator('#hypercrx-test-hover-anchor');
await expect(hoverAnchor).toBeVisible();
await expect(hoverAnchor).toHaveAttribute('data-hovercard-url', `/users/${developerName}/hovercard`);

await hoverAnchor.hover();
await page.waitForTimeout(800);
});

test('rendered OpenRank block carries developer identity metadata', async () => {
const page = await context.newPage();
await gotoPublicRepo(page);
await page.waitForTimeout(2500);
await mountSyntheticHovercardFixture(page);

const hoverAnchor = page.locator('#hypercrx-test-hover-anchor');
await hoverAnchor.hover();

const info = page.locator('.hypercrx-openrank-info').first();
await expect(info).toBeVisible({ timeout: 30000 });
await expect(info).toHaveAttribute('data-developer-name', developerName);
});
});
52 changes: 52 additions & 0 deletions tests/e2e/content-scripts/developer-networks.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { expect, test, type BrowserContext } from '@playwright/test';
import { cleanupExtensionContext, launchExtensionContext } from './helpers';

test.describe('content script component: developer networks', () => {
let context: BrowserContext;
let userDataDir: string;

test.beforeAll(async () => {
const launched = await launchExtensionContext('hypercrx-e2e-content-dev-networks-');
context = launched.context;
userDataDir = launched.userDataDir;
});

test.afterAll(async () => {
await cleanupExtensionContext(context, userDataDir);
});

test('renders perceptor section on a GitHub developer profile page', async () => {
const page = await context.newPage();
await page.goto('https://github.com/octocat', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(1800);

const perceptorHeading = page.locator('h2.h4.mb-2', { hasText: 'Perceptor' }).first();
await expect(perceptorHeading).toBeVisible({ timeout: 30000 });

const networkButtons = page.locator('span.hypercrx-label');
await expect(networkButtons).toHaveCount(3);
});

test('opens developer activity network modal with expected osgraph link', async () => {
const page = await context.newPage();
await page.goto('https://github.com/octocat', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(1800);

const firstNetworkButton = page.locator('span.hypercrx-label').first();
await expect(firstNetworkButton).toBeVisible({ timeout: 30000 });
await firstNetworkButton.click();

const modal = page.locator('div.ReactModal__Content_Custom').first();
await expect(modal).toBeVisible();
await expect(modal.locator('a[href*="osgraph.com/graphs/developer-activity/github/octocat"]')).toHaveCount(1);
});

test('does not render developer network controls on non-github pages', async () => {
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(1000);

await expect(page.locator('h2', { hasText: 'Perceptor' })).toHaveCount(0);
await expect(page.locator('span.hypercrx-label')).toHaveCount(0);
});
});
78 changes: 78 additions & 0 deletions tests/e2e/content-scripts/fast-pr.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { expect, test, type BrowserContext } from '@playwright/test';
import { cleanupExtensionContext, gotoPublicRepo, launchExtensionContext } from './helpers';

test.describe('content script component: fast-pr', () => {
let context: BrowserContext;
let userDataDir: string;

test.beforeAll(async () => {
const launched = await launchExtensionContext('hypercrx-e2e-content-fast-pr-');
context = launched.context;
userDataDir = launched.userDataDir;
});

test.afterAll(async () => {
await cleanupExtensionContext(context, userDataDir);
});

test('injects hidden sandbox iframe for fast-pr matching workflow', async () => {
const page = await context.newPage();
await gotoPublicRepo(page);

const sandboxFrame = page.locator('iframe#sandboxFrame');
await expect(sandboxFrame).toHaveCount(1);
await expect(sandboxFrame).toHaveAttribute('src', /sandbox\.html/);
});

test('renders floating fast-pr action after matchedUrl message', async () => {
const page = await context.newPage();
await gotoPublicRepo(page);

await page.evaluate(() => {
window.postMessage(
{
matchedUrl: {
filePath: 'README.md',
repoName: 'hypertrons/hypertrons-crx',
branch: 'master',
platform: 'Github',
horizontalRatio: 0.86,
verticalRatio: 0.84,
},
},
'*'
);
});

const featureRoot = page.locator('#hypercrx-fast-pr');
await expect(featureRoot).toHaveCount(1);
await expect(featureRoot.locator('.ant-float-btn')).toHaveCount(1);
});

test('updates fast-pr cache when sandbox update message is received', async () => {
const page = await context.newPage();
await gotoPublicRepo(page);

await page.evaluate(() => {
localStorage.removeItem('matchedUrlCache');
window.postMessage(
{
matchedFun: 'matched-by-rule',
isUpdated: true,
},
'*'
);
});

await expect
.poll(async () => {
return page.evaluate(() => {
const value = localStorage.getItem('matchedUrlCache');
if (!value) return null;
const parsed = JSON.parse(value);
return parsed.matchedFun;
});
})
.toBe('matched-by-rule');
});
});
Loading
Loading