Skip to content

Commit 57627f8

Browse files
authored
Merge pull request #5350 from JetBrains/e2e-tc-reporter
chore: improve production test runner on TeamCity
2 parents 1003aeb + ff9926b commit 57627f8

25 files changed

+144
-81
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,5 @@ generated
5050
/data/page_views_map.json
5151
test-results
5252

53-
public/data
53+
public/data
54+
test/storage-state.json

.teamcity/tests/buildTypes/E2EProductionTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ object E2EProductionTest : BuildType({
4848

4949
features {
5050
notifications {
51+
branchFilter = "+:master"
5152
enabled = !isProjectPlayground()
5253
notifierSettings = slackNotifier {
5354
connection = "PROJECT_EXT_486"

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"next-optimized-images": "3.0.0-canary.10",
108108
"next-transpile-modules": "^10.0.1",
109109
"playwright": "1.57.0",
110+
"playwright-teamcity-reporter": "1.0.5",
110111
"postcss-custom-media": "10.0.0",
111112
"postcss-import": "15.1.0",
112113
"prettier": "2.6.2",

playwright.config.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,28 @@ import { defineConfig, devices } from '@playwright/test';
22

33
const MAX_DIFF_PIXEL_RATIO = 0.025 as const;
44

5+
const isDevelopment = !process.env.CI;
6+
7+
const reporter = isDevelopment ? 'list' : 'playwright-teamcity-reporter';
8+
const retries = isDevelopment ? 0 : 2;
9+
const timeout = isDevelopment ? 10000 : 5000;
10+
11+
const forbidOnly = !isDevelopment;
12+
513
export default defineConfig({
6-
forbidOnly: !!process.env.CI,
7-
retries: process.env.CI ? 2 : 0,
8-
reporter: process.env.CI ? 'dot' : 'list',
14+
globalSetup: require.resolve('./test/global-setup.ts'),
15+
forbidOnly,
16+
retries,
17+
reporter,
918
snapshotDir: 'test/snapshots',
1019
expect: {
20+
timeout,
1121
toMatchSnapshot: { maxDiffPixelRatio: MAX_DIFF_PIXEL_RATIO },
1222
toHaveScreenshot: { maxDiffPixelRatio: MAX_DIFF_PIXEL_RATIO }
1323
},
1424
use: {
15-
baseURL: process.env.BASE_URL || 'http://localhost:9000',
25+
baseURL: process.env.BASE_URL || 'http://localhost:3000',
26+
storageState: 'test/storage-state.json',
1627
trace: 'off',
1728
screenshot: 'only-on-failure',
1829
video: 'retain-on-failure'

test/e2e/teach/courses.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import { expect, test } from '@playwright/test';
22
import { CoursesPage } from '../../page/teach/courses-page';
3-
import { closeExternalBanners } from '../utils';
43
import { testSelector } from '../../utils';
54
import { checkTeachCta, checkTeachMap, checkTeachNav } from './utils';
65

76
test.describe('Courses page appearance and functionality', async () => {
87
test.beforeEach(async ({ page, context, baseURL }) => {
98
const coursesPage = new CoursesPage(page);
109
await coursesPage.init();
11-
await closeExternalBanners(context, page, baseURL);
1210
});
1311

1412
test('Should load the courses page correctly', async ({ page }) => {

test/e2e/teach/education.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import { expect, test } from '@playwright/test';
22
import { TeachPage } from '../../page/teach/education';
3-
import { closeExternalBanners } from '../utils';
43
import { testSelector } from '../../utils';
54
import { checkTeachCta, checkTeachMap, checkTeachNav, MAILTO_LINK, MATERIALS_LINK, SIGNUP_LINK } from './utils';
65

76
test.describe('Education landing page content and interactions', async () => {
8-
test.beforeEach(async ({ context, page, baseURL }) => {
7+
test.beforeEach(async ({ page }) => {
98
const teachPage = new TeachPage(page);
109
await teachPage.init();
11-
await closeExternalBanners(context, page, baseURL);
1210
});
1311

1412
test('Should load the education page correctly', async ({ page }) => {

test/e2e/teach/why.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { expect, test } from '@playwright/test';
22
import { WhyTeachPage } from '../../page/teach/why-page';
3-
import { closeExternalBanners } from '../utils';
43
import { checkTeachCta, checkTeachNav } from './utils';
54
import { testSelector } from '../../utils';
65

@@ -21,10 +20,9 @@ function toId(label: typeof LIST_OF_SECTION[number][0]) {
2120
}
2221

2322
test.describe('Why Teach Kotlin page appearance and functionality', async () => {
24-
test.beforeEach(async ({ page, context, baseURL }) => {
23+
test.beforeEach(async ({ page }) => {
2524
const whyTeachPage = new WhyTeachPage(page);
2625
await whyTeachPage.init();
27-
await closeExternalBanners(context, page, baseURL);
2826
});
2927

3028
test('Should load the Why Teach Kotlin page correctly', async ({ page }) => {

test/e2e/utils.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { BrowserContext, ElementHandle, expect, Page, test } from '@playwright/test';
2-
import { closeCookiesConsentBanner, isSkipScreenshot } from '../utils';
1+
import { ElementHandle, expect, Page, test } from '@playwright/test';
2+
import { isSkipScreenshot } from '../utils';
33
import { PageAssertionsToHaveScreenshotOptions } from 'playwright/types/test';
44

55
export async function getElementScreenshotWithPadding(page: Page, element: ElementHandle, padding: number): Promise<Buffer | undefined> {
@@ -18,16 +18,6 @@ export async function getElementScreenshotWithPadding(page: Page, element: Eleme
1818
}
1919
}
2020

21-
export async function closeExternalBanners(context: BrowserContext, page: Page, baseUrl: string) {
22-
if (baseUrl.startsWith('https://kotlinlang.org/')) {
23-
await closeCookiesConsentBanner(context, baseUrl);
24-
} else {
25-
await page.frameLocator('#webpack-dev-server-client-overlay')
26-
.locator('[aria-label="Dismiss"]')
27-
.click();
28-
}
29-
}
30-
3121
export function pageWrapperMask(page: Page) {
3222
return [
3323
page.locator('header[data-test="header"]'),

test/global-setup.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { join } from 'node:path';
2+
import { writeFile } from 'node:fs/promises';
3+
import { chromium, FullConfig } from '@playwright/test';
4+
import { isProduction } from './utils';
5+
6+
export default async function globalSetup(config: FullConfig) {
7+
const storageStatePath = join(__dirname, 'storage-state.json');
8+
await writeFile(storageStatePath, '{}', 'utf-8');
9+
10+
const project = config.projects[0];
11+
console.log(`[Global Setup] Processing project ${project.name}`);
12+
const baseURL = project.use?.baseURL;
13+
14+
if (isProduction(baseURL)) {
15+
await closeProductionElements(baseURL, storageStatePath);
16+
}
17+
}
18+
19+
async function closeProductionElements(baseURL: string, storageStatePath: string) {
20+
console.log(`[Global Setup] Starting cookie banner setup for ${baseURL}`);
21+
22+
const browser = await chromium.launch();
23+
const context = await browser.newContext();
24+
const page = await context.newPage();
25+
26+
try {
27+
await page.goto(baseURL, { waitUntil: 'domcontentloaded' });
28+
29+
try {
30+
const acceptButton = page.getByRole('button', { name: 'Accept All' });
31+
await acceptButton.waitFor({ state: 'visible', timeout: 5000 });
32+
await acceptButton.click();
33+
34+
await page.waitForTimeout(1000);
35+
} catch (error) {
36+
console.log('[Global Setup] Cookie banner not found - continuing');
37+
}
38+
39+
const closeBanner = page.locator('#optly-banner_close');
40+
41+
if (await closeBanner.count() > 0) {
42+
console.log('[Global Setup] Closing "purple" banner');
43+
await closeBanner.click();
44+
await page.waitForSelector('#optly-banner_close', { state: 'hidden' });
45+
}
46+
47+
await context.storageState({ path: storageStatePath });
48+
console.log(`[Global Setup] Storage state saved to ${storageStatePath}`);
49+
} catch (error) {
50+
console.error('[Global Setup] Error during setup:', error);
51+
throw error;
52+
} finally {
53+
await context.close();
54+
await browser.close();
55+
}
56+
}

test/production/api-navigation.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import { expect, Page, test } from '@playwright/test';
33
test.describe('Api navigation', () => {
44
test.beforeEach(async ({ page }) => {
55
await page.goto('/');
6-
await page.waitForSelector('button.ch2-btn.ch2-btn-primary');
7-
await page.click('button.ch2-btn.ch2-btn-primary');
86
const navbar = page.locator('[data-test="header"]');
97
const apiButton = navbar.getByText('API', { exact: true });
108
await expect(apiButton).toBeVisible();

0 commit comments

Comments
 (0)