From f620a4af3c986467a188733acfaf1be4cdc9b69e Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Wed, 12 Aug 2026 13:35:48 -0300 Subject: [PATCH 01/22] Migrate wrappers e2e tests from TestCafe to Playwright --- .github/workflows/wrapper_tests_e2e.yml | 10 +- apps/react/package.json | 1 - apps/react/project.json | 1 - apps/react/runner.js | 17 --- apps/react/test.js | 8 -- apps/vue/package.json | 1 - apps/vue/project.json | 1 - apps/vue/runner.js | 17 --- apps/vue/test.js | 8 -- e2e/wrappers/.gitignore | 2 + e2e/wrappers/README.md | 44 ++++++++ e2e/wrappers/docker/Dockerfile | 14 +++ e2e/wrappers/docker/run.sh | 31 +++++ e2e/wrappers/fixtures.ts | 13 +++ e2e/wrappers/package.json | 9 +- e2e/wrappers/playwright.config.ts | 46 ++++++++ e2e/wrappers/runner.js | 106 ------------------ e2e/wrappers/serve.js | 38 +++++++ e2e/wrappers/test-helpers.js | 22 ---- e2e/wrappers/tests/button.spec.ts | 11 ++ e2e/wrappers/tests/button.test.js | 11 -- .../tests/chat-template-rerender.spec.ts | 25 +++++ .../tests/chat-template-rerender.test.js | 21 ---- .../tests/gantt-template-state-update.spec.ts | 19 ++++ .../tests/gantt-template-state-update.test.js | 17 --- .../tests/inputs-list-in-form.spec.ts | 22 ++++ .../tests/inputs-list-in-form.test.js | 22 ---- .../tests/select-box-nested-validator.spec.ts | 39 +++++++ .../tests/select-box-nested-validator.test.js | 41 ------- e2e/wrappers/tests/textbox.spec.ts | 27 +++++ e2e/wrappers/tests/textbox.test.js | 25 ----- pnpm-lock.yaml | 47 +++++++- pnpm-workspace.yaml | 1 + 33 files changed, 384 insertions(+), 333 deletions(-) delete mode 100644 apps/react/runner.js delete mode 100644 apps/react/test.js delete mode 100644 apps/vue/runner.js delete mode 100644 apps/vue/test.js create mode 100644 e2e/wrappers/.gitignore create mode 100644 e2e/wrappers/README.md create mode 100644 e2e/wrappers/docker/Dockerfile create mode 100755 e2e/wrappers/docker/run.sh create mode 100644 e2e/wrappers/fixtures.ts create mode 100644 e2e/wrappers/playwright.config.ts delete mode 100644 e2e/wrappers/runner.js create mode 100644 e2e/wrappers/serve.js delete mode 100644 e2e/wrappers/test-helpers.js create mode 100644 e2e/wrappers/tests/button.spec.ts delete mode 100644 e2e/wrappers/tests/button.test.js create mode 100644 e2e/wrappers/tests/chat-template-rerender.spec.ts delete mode 100644 e2e/wrappers/tests/chat-template-rerender.test.js create mode 100644 e2e/wrappers/tests/gantt-template-state-update.spec.ts delete mode 100644 e2e/wrappers/tests/gantt-template-state-update.test.js create mode 100644 e2e/wrappers/tests/inputs-list-in-form.spec.ts delete mode 100644 e2e/wrappers/tests/inputs-list-in-form.test.js create mode 100644 e2e/wrappers/tests/select-box-nested-validator.spec.ts delete mode 100644 e2e/wrappers/tests/select-box-nested-validator.test.js create mode 100644 e2e/wrappers/tests/textbox.spec.ts delete mode 100644 e2e/wrappers/tests/textbox.test.js diff --git a/.github/workflows/wrapper_tests_e2e.yml b/.github/workflows/wrapper_tests_e2e.yml index fff07fe970d1..336fedc81790 100644 --- a/.github/workflows/wrapper_tests_e2e.yml +++ b/.github/workflows/wrapper_tests_e2e.yml @@ -170,12 +170,6 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Start server for ${{ matrix.framework }} - working-directory: e2e/wrappers - run: | - pnpm run start:${{ matrix.framework }} & - sleep 10 - - name: Run tests for ${{ matrix.framework }} working-directory: e2e/wrappers run: pnpm run test:${{ matrix.framework }} @@ -185,5 +179,7 @@ jobs: uses: actions/upload-artifact@v7 with: name: test-fails-${{matrix.framework}} - path: e2e/wrappers/screenshots + path: | + e2e/wrappers/playwright-report + e2e/wrappers/test-results if-no-files-found: ignore diff --git a/apps/react/package.json b/apps/react/package.json index 626d1c0e6cbf..096562318540 100644 --- a/apps/react/package.json +++ b/apps/react/package.json @@ -27,7 +27,6 @@ "webpack-dev-server": "5.2.6" }, "scripts": { - "test": "node runner.js", "start": "webpack-dev-server", "build": "webpack" }, diff --git a/apps/react/project.json b/apps/react/project.json index 42973c8ad028..5dfa1a454156 100644 --- a/apps/react/project.json +++ b/apps/react/project.json @@ -15,7 +15,6 @@ "!{projectRoot}/public/js/app/bundle*", "{projectRoot}/*.tsx", "{projectRoot}/*.js", - "!{projectRoot}/test.js", "{projectRoot}/tsconfig.json", "{workspaceRoot}/tsconfig.json" ], diff --git a/apps/react/runner.js b/apps/react/runner.js deleted file mode 100644 index ed29b45f45c2..000000000000 --- a/apps/react/runner.js +++ /dev/null @@ -1,17 +0,0 @@ -const process = require('process'); -const createTestCafe = require('testcafe'); - -let testCafe; -createTestCafe('127.0.0.1') - .then(tsc => { - testCafe = tsc; - - const runner = tsc.createRunner(); - return runner - .src('test.js') - .browsers('chrome:headless --disable-gpu --window-size=1200,800') - .run(); - }).then(failedCount => { - testCafe.close(); - process.exit(failedCount); - }); diff --git a/apps/react/test.js b/apps/react/test.js deleted file mode 100644 index e7d5cbb051bb..000000000000 --- a/apps/react/test.js +++ /dev/null @@ -1,8 +0,0 @@ -const path = require('path'); - -fixture('DevExtreme React Playground') - .page(path.resolve(__dirname, 'public/index.html')); - -test('App has no errors', async (t) => { - -}); diff --git a/apps/vue/package.json b/apps/vue/package.json index c20c2aa1cf35..b314f15ab54f 100644 --- a/apps/vue/package.json +++ b/apps/vue/package.json @@ -33,7 +33,6 @@ "webpack-dev-server": "5.2.6" }, "scripts": { - "test": "node runner.js", "start": "webpack-dev-server", "build": "webpack" } diff --git a/apps/vue/project.json b/apps/vue/project.json index 0a42ced30f0c..14a07d7f08d6 100644 --- a/apps/vue/project.json +++ b/apps/vue/project.json @@ -16,7 +16,6 @@ "{projectRoot}/*.ts", "{projectRoot}/*.vue", "{projectRoot}/*.js", - "!{projectRoot}/test.js", "{projectRoot}/tsconfig.json" ], "outputs": [ diff --git a/apps/vue/runner.js b/apps/vue/runner.js deleted file mode 100644 index 953758023537..000000000000 --- a/apps/vue/runner.js +++ /dev/null @@ -1,17 +0,0 @@ -const process = require('process'); -const createTestCafe = require('testcafe'); - -let testCafe; -createTestCafe('localhost') - .then(tsc => { - testCafe = tsc; - - const runner = tsc.createRunner(); - return runner - .src('test.js') - .browsers('chrome:headless --disable-gpu --window-size=1200,800') - .run(); - }).then(failedCount => { - testCafe.close(); - process.exit(failedCount); - }); diff --git a/apps/vue/test.js b/apps/vue/test.js deleted file mode 100644 index 5cf6e3127730..000000000000 --- a/apps/vue/test.js +++ /dev/null @@ -1,8 +0,0 @@ -const path = require('path'); - -fixture('DevExtreme Vue Playground') - .page(path.resolve(__dirname, 'public/index.html')); - -test('App has no errors', async (t) => { - -}); diff --git a/e2e/wrappers/.gitignore b/e2e/wrappers/.gitignore new file mode 100644 index 000000000000..5c4ffa21fa5e --- /dev/null +++ b/e2e/wrappers/.gitignore @@ -0,0 +1,2 @@ +playwright-report/ +test-results/ diff --git a/e2e/wrappers/README.md b/e2e/wrappers/README.md new file mode 100644 index 000000000000..3e2de13330db --- /dev/null +++ b/e2e/wrappers/README.md @@ -0,0 +1,44 @@ +# Wrappers E2E tests + +End-to-end tests that check DevExtreme components inside the React, Vue and Angular wrappers. +The tests are written with [Playwright](https://playwright.dev/) and use the system Google Chrome +(`channel: 'chrome'`), so no browser download is needed. + +## Layout + +| Path | Purpose | +|------------------------|----------------------------------------------------------------------| +| `builders/*` | Host applications that render the examples for each framework | +| `examples/*` | Examples under test, one folder per scenario and framework | +| `tests/*.spec.ts` | Playwright specs | +| `fixtures.ts` | `test`/`expect` with the `framework` option added | +| `playwright.config.ts` | Per-framework port, base URL and dev server | +| `serve.js` | Static server for a built application | +| `docker/` | Container that matches the CI environment | + +## Run locally + +```bash +pnpm install +pnpm nx all:build-testing workflows + +cd e2e/wrappers +pnpm run build:react19 # or build:vue3 / build:angular / build:all + +pnpm run test:react19 # or test:vue3 / test:angular +``` + +`playwright test` starts `serve.js` on its own, so no separate server is needed. +Useful flags: `--headed`, `--debug`, `--ui`, `--reporter=html`. + +Examples that exist for a single framework only (Chat, Gantt) are skipped in the other frameworks. + +## Run in the CI environment + +The container pins Node and Google Chrome to the versions used by the `Wrappers E2E Tests` +workflow. Dependencies and application builds are taken from the host: + +```bash +cd e2e/wrappers +docker/run.sh react19 +``` diff --git a/e2e/wrappers/docker/Dockerfile b/e2e/wrappers/docker/Dockerfile new file mode 100644 index 000000000000..bcde5274689c --- /dev/null +++ b/e2e/wrappers/docker/Dockerfile @@ -0,0 +1,14 @@ +# Reproduces the "Wrappers E2E Tests" CI environment: Node from .node-version +# and Google Chrome pinned to the version used by .github/actions/setup-chrome. +FROM node:24.16.0-bookworm-slim + +ARG CHROME_VERSION=150.0.7871.181 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl \ + && curl -fsSL "https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}-1_amd64.deb" -o /tmp/chrome.deb \ + && apt-get install -y --no-install-recommends /tmp/chrome.deb \ + && rm /tmp/chrome.deb \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /repo/e2e/wrappers diff --git a/e2e/wrappers/docker/run.sh b/e2e/wrappers/docker/run.sh new file mode 100755 index 000000000000..8e080ac76a51 --- /dev/null +++ b/e2e/wrappers/docker/run.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Runs the wrappers e2e tests in a container that matches the CI environment. +# The repository is mounted as is, so dependencies must be installed and +# the app under test built on the host first: +# +# pnpm install +# pnpm nx all:build-testing workflows +# cd e2e/wrappers && pnpm run build:react19 +# +# Usage: docker/run.sh [react19|vue3|angular] [extra playwright args] + +set -euo pipefail + +FRAMEWORK="${1:-react19}" +shift || true + +DOCKER_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$DOCKER_DIR/../../.." && pwd)" + +# Google Chrome for Linux ships for amd64 only, so the platform is pinned as on CI. +PLATFORM=linux/amd64 + +docker build --platform "$PLATFORM" -t devextreme-wrappers-e2e "$DOCKER_DIR" + +docker run --rm --ipc=host --platform "$PLATFORM" \ + -v "$REPO_ROOT:/repo" \ + -w /repo/e2e/wrappers \ + -e CI=true \ + -e "FRAMEWORK=$FRAMEWORK" \ + devextreme-wrappers-e2e \ + node_modules/.bin/playwright test "$@" diff --git a/e2e/wrappers/fixtures.ts b/e2e/wrappers/fixtures.ts new file mode 100644 index 000000000000..3ce3100c6f9d --- /dev/null +++ b/e2e/wrappers/fixtures.ts @@ -0,0 +1,13 @@ +import { test as base } from '@playwright/test'; + +export type Framework = 'react19' | 'vue3' | 'angular'; + +export interface TestOptions { + framework: Framework; +} + +export const test = base.extend({ + framework: ['react19', { option: true }], +}); + +export { expect } from '@playwright/test'; diff --git a/e2e/wrappers/package.json b/e2e/wrappers/package.json index 7ba5c5c8644e..e8f3b42f52b3 100644 --- a/e2e/wrappers/package.json +++ b/e2e/wrappers/package.json @@ -13,9 +13,9 @@ "start:vue3": "pnpm run dev:vue3 -- --host", "start:angular": "cd builders/angular && pnpm run start", "build:all": "pnpm run build:react19 && pnpm run build:vue3 && pnpm run build:angular", - "test:react19": "node ./runner.js --framework=react", - "test:vue3": "node ./runner.js --framework=vue", - "test:angular": "node ./runner.js --framework=angular" + "test:react19": "cross-env FRAMEWORK=react19 playwright test", + "test:vue3": "cross-env FRAMEWORK=vue3 playwright test", + "test:angular": "cross-env FRAMEWORK=angular playwright test" }, "dependencies": { "@angular/common": "catalog:angular", @@ -59,11 +59,13 @@ "@angular/cli": "catalog:angular", "@angular/compiler-cli": "catalog:angular", "@eslint/js": "catalog:", + "@playwright/test": "catalog:", "@types/jasmine": "5.1.4", "@types/react": "19.1.2", "@types/react-dom": "19.1.3", "@vitejs/plugin-react": "4.7.0", "@vitejs/plugin-vue": "5.2.4", + "cross-env": "7.0.3", "eslint": "9.39.4", "eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-react-refresh": "0.5.2", @@ -74,7 +76,6 @@ "karma-coverage": "2.2.1", "karma-jasmine": "5.1.0", "karma-jasmine-html-reporter": "2.1.0", - "testcafe": "catalog:", "typescript": "5.8.3", "vite": "8.0.16" }, diff --git a/e2e/wrappers/playwright.config.ts b/e2e/wrappers/playwright.config.ts new file mode 100644 index 000000000000..dc115d9d7962 --- /dev/null +++ b/e2e/wrappers/playwright.config.ts @@ -0,0 +1,46 @@ +import { defineConfig } from '@playwright/test'; + +import type { Framework, TestOptions } from './fixtures'; + +const FRAMEWORK_PORTS: Record = { + react19: 3030, + angular: 3031, + vue3: 3032, +}; + +const framework = (process.env.FRAMEWORK ?? 'react19') as Framework; +const port = FRAMEWORK_PORTS[framework]; + +if(!port) { + throw new Error(`Unsupported framework: ${framework}. Expected one of: ${Object.keys(FRAMEWORK_PORTS).join(', ')}.`); +} + +const baseURL = `http://localhost:${port}`; + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + workers: 1, + reporter: process.env.CI + ? [['list'], ['html', { open: 'never' }]] + : [['list']], + timeout: 30000, + expect: { timeout: 5000 }, + use: { + framework, + baseURL, + channel: 'chrome', + headless: true, + viewport: { width: 1200, height: 800 }, + launchOptions: { args: ['--no-sandbox', '--disable-gpu'] }, + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + }, + webServer: { + command: `node ./serve.js --framework=${framework} --port=${port}`, + url: baseURL, + reuseExistingServer: !process.env.CI, + timeout: 30000, + }, +}); diff --git a/e2e/wrappers/runner.js b/e2e/wrappers/runner.js deleted file mode 100644 index 13e6d6cec50f..000000000000 --- a/e2e/wrappers/runner.js +++ /dev/null @@ -1,106 +0,0 @@ -const path = require('path'); -const process = require('process'); -const minimist = require('minimist'); -const express = require('express'); -const createTestCafe = require('testcafe'); - -const argv = minimist(process.argv.slice(2)); -const framework = argv.framework || 'react'; - -const http = require('http'); - -function waitForServerReady(port, timeout = 5000) { - const deadline = Date.now() + timeout; - - return new Promise((resolve, reject) => { - const check = () => { - http.get(`http://localhost:${port}`, res => { - res.destroy(); - resolve(); - }).on('error', () => { - if (Date.now() > deadline) { - reject(new Error(`Timeout waiting for http://localhost:${port}`)); - } else { - setTimeout(check, 100); - } - }); - }; - - check(); - }); -} - -const frameworkConfig = { - react: { - staticPath: path.resolve(__dirname, 'builders/react19/dist/index.html'), - port: 3030, - }, - angular: { - staticPath: path.resolve(__dirname, 'builders/angular/dist/angular/browser/index.html'), - port: 3031, - }, - vue: { - staticPath: path.resolve(__dirname, 'builders/vue3/dist/index.html'), - port: 3032, - }, -}; - -if (!frameworkConfig[framework]) { - console.error(`❌ Unsupported framework: ${framework}`); - process.exit(1); -} - -const { staticPath, port } = frameworkConfig[framework]; -process.env.FRAMEWORK = framework; -process.env.E2E_TEST_PORT = port; - -const startStaticServer = () => - new Promise((resolve, reject) => { - const app = express(); - app.use(express.static(path.dirname(staticPath))); - app.get('*', (_, res) => res.sendFile(staticPath)); - - const server = app.listen(port, () => { - console.log(`✅ Server for ${framework} running at http://localhost:${port}`); - resolve(server); - }); - - server.on('error', reject); - }); - -(async () => { - let testcafe; - let server; - - try { - server = await startStaticServer(); - - await waitForServerReady(port, 5000); - - testcafe = await createTestCafe(); - - const runner = testcafe.createRunner(); - - const failedCount = await runner - .src('./tests/**/*.js') - .browsers(process.env.BROWSER || 'chrome:headless --no-sandbox --disable-gpu --window-size=1200,800') - .concurrency(1) - .run({ - skipJsErrors: true, - selectorTimeout: 3000, - assertionTimeout: 1000, - pageLoadTimeout: 5000, - }); - - console.log(`✅ E2E complete. Failed tests: ${failedCount}`); - process.exit(failedCount); - - } catch (err) { - console.error('❌ E2E test run failed:', err); - process.exit(1); - - } finally { - if (testcafe) await testcafe.close(); - if (server && server.close) await new Promise((res) => server.close(res)); - } -})(); diff --git a/e2e/wrappers/serve.js b/e2e/wrappers/serve.js new file mode 100644 index 000000000000..37b8ca11cab6 --- /dev/null +++ b/e2e/wrappers/serve.js @@ -0,0 +1,38 @@ +const fs = require('fs'); +const path = require('path'); +const process = require('process'); +const minimist = require('minimist'); +const express = require('express'); + +const DIST_DIRS = { + react19: 'builders/react19/dist', + angular: 'builders/angular/dist/angular/browser', + vue3: 'builders/vue3/dist', +}; + +const argv = minimist(process.argv.slice(2)); +const framework = argv.framework; +const port = Number(argv.port); +const distDir = DIST_DIRS[framework]; + +if(!distDir) { + console.error(`❌ Unsupported framework: ${framework}. Expected one of: ${Object.keys(DIST_DIRS).join(', ')}.`); + process.exit(1); +} + +const root = path.resolve(__dirname, distDir); +const indexPath = path.join(root, 'index.html'); + +if(!fs.existsSync(indexPath)) { + console.error(`❌ Build for ${framework} is not found at ${root}. Run "pnpm run build:${framework}" first.`); + process.exit(1); +} + +const app = express(); + +app.use(express.static(root)); +app.get('*', (_, res) => res.sendFile(indexPath)); + +app.listen(port, () => { + console.log(`✅ Server for ${framework} running at http://localhost:${port}`); +}); diff --git a/e2e/wrappers/test-helpers.js b/e2e/wrappers/test-helpers.js deleted file mode 100644 index be9fce8d132a..000000000000 --- a/e2e/wrappers/test-helpers.js +++ /dev/null @@ -1,22 +0,0 @@ -import { fixture, test } from 'testcafe'; -const process = require('process'); - -export function navigateToComponent(t, baseUrl, examplePath) { - return t.navigateTo(`${baseUrl}/examples/${examplePath}`); -} - -export function testInFramework(fixtureName, examplePath, ...testData) { - const frameworkName = process.env.FRAMEWORK || 'react'; - const port = process.env.E2E_TEST_PORT || 3030; - const baseUrl = `http://localhost:${port}`; - - fixture(fixtureName).page(baseUrl); - - for(let [testName, testFn] of testData) { - test(testName, async t => { - await navigateToComponent(t, baseUrl, examplePath); - - await testFn(t); - }); - } -} diff --git a/e2e/wrappers/tests/button.spec.ts b/e2e/wrappers/tests/button.spec.ts new file mode 100644 index 000000000000..a50dc9b01d58 --- /dev/null +++ b/e2e/wrappers/tests/button.spec.ts @@ -0,0 +1,11 @@ +import { expect, test } from '../fixtures'; + +test.describe('Button scenarios', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/examples/button'); + }); + + test('Button should exist', async ({ page }) => { + await expect(page.locator('.dx-button-text')).toBeAttached(); + }); +}); diff --git a/e2e/wrappers/tests/button.test.js b/e2e/wrappers/tests/button.test.js deleted file mode 100644 index 7966bf3799bc..000000000000 --- a/e2e/wrappers/tests/button.test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Selector } from 'testcafe'; -import { testInFramework } from '../test-helpers'; - -testInFramework('Button scenarios', 'button', [ - 'Button should exist', - async (t) => { - const button = Selector('.dx-button-text'); - - await t.expect(button.exists).ok('Button should exist'); - } -]); \ No newline at end of file diff --git a/e2e/wrappers/tests/chat-template-rerender.spec.ts b/e2e/wrappers/tests/chat-template-rerender.spec.ts new file mode 100644 index 000000000000..025341454d66 --- /dev/null +++ b/e2e/wrappers/tests/chat-template-rerender.spec.ts @@ -0,0 +1,25 @@ +import { expect, test } from '../fixtures'; + +test.describe('Chat template re-rendering', () => { + test.skip(({ framework }) => framework !== 'react19', 'The example is implemented for React only'); + + test.beforeEach(async ({ page }) => { + await page.goto('/examples/chat-template-rerender'); + }); + + test('Chat should be able to re-render its messages', async ({ page }) => { + const textarea = page.locator('.dx-chat-messagebox textarea'); + const sendButton = page.locator('.dx-chat-messagebox .dx-chat-textarea-toolbar .dx-button'); + const assistantBubble = page.locator('.chat-messagebubble-text').nth(1); + const regenerateButton = page.locator('.dx-icon-refresh').nth(1); + + await textarea.pressSequentially('Hi there!'); + await sendButton.click(); + + await expect(assistantBubble).toHaveText('How can I help you?'); + + await regenerateButton.click(); + + await expect(assistantBubble).toHaveText('In other words, what do you want?'); + }); +}); diff --git a/e2e/wrappers/tests/chat-template-rerender.test.js b/e2e/wrappers/tests/chat-template-rerender.test.js deleted file mode 100644 index aa327c8f73ce..000000000000 --- a/e2e/wrappers/tests/chat-template-rerender.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Selector } from 'testcafe'; -import { testInFramework } from '../test-helpers'; - -if(process.env.FRAMEWORK === 'react') { - testInFramework('Chat template re-rendering', 'chat-template-rerender', [ - 'Chat should be able to re-render its messages', - async (t) => { - const textarea = Selector('.dx-chat-messagebox textarea'); - const sendButton = Selector('.dx-chat-messagebox .dx-chat-textarea-toolbar .dx-button'); - const assistantBubble = Selector('.chat-messagebubble-text').nth(1); - const regenerateButton = Selector('.dx-icon-refresh').nth(1); - - await t - .typeText(textarea, 'Hi there!') - .click(sendButton) - .expect(assistantBubble.textContent).eql('How can I help you?') - .click(regenerateButton) - .expect(assistantBubble.textContent).eql('In other words, what do you want?'); - } - ]); -} diff --git a/e2e/wrappers/tests/gantt-template-state-update.spec.ts b/e2e/wrappers/tests/gantt-template-state-update.spec.ts new file mode 100644 index 000000000000..cddc05d042b1 --- /dev/null +++ b/e2e/wrappers/tests/gantt-template-state-update.spec.ts @@ -0,0 +1,19 @@ +import { expect, test } from '../fixtures'; + +test.describe('Gantt template state update', () => { + test.skip(({ framework }) => framework !== 'react19', 'The example is implemented for React only'); + + test.beforeEach(async ({ page }) => { + await page.goto('/examples/gantt-template-state-update'); + }); + + test('Gantt should be able to unmount its template when a parent component\'s state update happens', async ({ page }) => { + const hideButton = page.locator('button', { hasText: 'Hide Data' }); + const noDataPlaceholder = page.locator('.dx-treelist-nodata'); + + await hideButton.click(); + + await expect(noDataPlaceholder).toBeVisible(); + await expect(noDataPlaceholder).toHaveText('No data'); + }); +}); diff --git a/e2e/wrappers/tests/gantt-template-state-update.test.js b/e2e/wrappers/tests/gantt-template-state-update.test.js deleted file mode 100644 index 7d402aaeab4b..000000000000 --- a/e2e/wrappers/tests/gantt-template-state-update.test.js +++ /dev/null @@ -1,17 +0,0 @@ -import { Selector } from 'testcafe'; -import { testInFramework } from '../test-helpers'; - -if(process.env.FRAMEWORK === 'react') { - testInFramework('Gantt template state update', 'gantt-template-state-update', [ - 'Gantt should be able to ummount its template when a parent component\'s state update happens', - async (t) => { - const hideButton = Selector('button').withText('Hide Data'); - const noDataPlaceholder = Selector('.dx-treelist-nodata'); - - await t - .click(hideButton) - .expect(noDataPlaceholder.visible).ok() - .expect(noDataPlaceholder.textContent).eql('No data'); - } - ]); -} diff --git a/e2e/wrappers/tests/inputs-list-in-form.spec.ts b/e2e/wrappers/tests/inputs-list-in-form.spec.ts new file mode 100644 index 000000000000..430b228820e7 --- /dev/null +++ b/e2e/wrappers/tests/inputs-list-in-form.spec.ts @@ -0,0 +1,22 @@ +import { expect, test } from '../fixtures'; + +test.describe('inputs-list-in-form scenarios', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/examples/inputs-list-in-form'); + }); + + test('Phone inputs should adding and deleting correctly', async ({ page }) => { + const addButton = page.locator('.dx-button-text', { hasText: 'Add phone' }); + const deleteButton = page.locator('.dx-button', { has: page.locator('.dx-icon-trash') }); + const phoneGroup = page.locator('[aria-labelledby$="_phones-container"]'); + const phoneInputs = phoneGroup.locator('.dx-texteditor-container'); + + await expect(phoneInputs).toHaveCount(0); + + await addButton.click(); + await expect(phoneInputs).toHaveCount(1); + + await deleteButton.click(); + await expect(phoneInputs).toHaveCount(0); + }); +}); diff --git a/e2e/wrappers/tests/inputs-list-in-form.test.js b/e2e/wrappers/tests/inputs-list-in-form.test.js deleted file mode 100644 index ae61e713564a..000000000000 --- a/e2e/wrappers/tests/inputs-list-in-form.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Selector } from 'testcafe'; -import { testInFramework } from '../test-helpers'; - -testInFramework('inputs-list-in-form scenarios', 'inputs-list-in-form', [ - 'Phone inputs should adding and deleting correctly', - async (t) => { - const addButton = Selector('.dx-button-text').withText('Add phone'); - const deleteButton = Selector('.dx-icon-trash').parent('.dx-button'); - - const phoneGroup = Selector('[aria-labelledby$="_phones-container"]'); - - const phoneInputs = phoneGroup.find('.dx-texteditor-container'); - - await t.expect(phoneInputs.count).eql(0, 'Phone inputs should not exist initially'); - - await t.click(addButton); - await t.expect(phoneInputs.count).eql(1, 'There should be exactly one phone input after adding'); - - await t.click(deleteButton); - await t.expect(phoneInputs.count).eql(0, 'Phone inputs should be removed after deletion'); - } -]); diff --git a/e2e/wrappers/tests/select-box-nested-validator.spec.ts b/e2e/wrappers/tests/select-box-nested-validator.spec.ts new file mode 100644 index 000000000000..df26be374309 --- /dev/null +++ b/e2e/wrappers/tests/select-box-nested-validator.spec.ts @@ -0,0 +1,39 @@ +import { expect, test } from '../fixtures'; + +test.describe('SelectBox nested validator scenarios', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/examples/select-box-nested-validator'); + }); + + test('SelectBox with nested Validator component should not render double errors', async ({ page }) => { + const validateButton = page.locator('.dx-button-text', { hasText: 'Validate' }); + const validationSummary = page.locator('.dx-validationsummary'); + + await expect(validateButton).toBeAttached(); + await expect(validationSummary).toBeAttached(); + + await validateButton.click(); + + const validationSummaryItems = validationSummary.locator('.dx-validationsummary-item'); + + await expect(validationSummaryItems).toHaveCount(1); + await expect(validationSummaryItems.first()).toHaveText('Type is required'); + }); + + test('SelectBox validation should pass when value is selected', async ({ page }) => { + const validateButton = page.locator('.dx-button-text', { hasText: 'Validate' }); + const validationSummary = page.locator('.dx-validationsummary'); + const selectBoxArrow = page.locator('.dx-selectbox .dx-dropdowneditor-button'); + + await selectBoxArrow.click(); + + const firstItem = page.locator('.dx-item', { hasText: 'One' }); + + await expect(firstItem).toBeVisible(); + await firstItem.click(); + + await validateButton.click(); + + await expect(validationSummary.locator('.dx-validationsummary-item')).toHaveCount(0); + }); +}); diff --git a/e2e/wrappers/tests/select-box-nested-validator.test.js b/e2e/wrappers/tests/select-box-nested-validator.test.js deleted file mode 100644 index 3a7f7f83f328..000000000000 --- a/e2e/wrappers/tests/select-box-nested-validator.test.js +++ /dev/null @@ -1,41 +0,0 @@ -import { Selector } from 'testcafe'; -import { testInFramework } from '../test-helpers'; - -testInFramework('SelectBox nested validator scenarios', 'select-box-nested-validator', [ - 'SelectBox with nested Validator component should not render double errors', - async (t) => { - const validateButton = Selector('.dx-button-text').withText('Validate'); - const validationSummary = Selector('.dx-validationsummary'); - - await t - .expect(validateButton.exists).ok('Validate button should exist') - .expect(validationSummary.exists).ok('Validation summary should exist'); - - await t.click(validateButton); - - const updatedValidationSummaryItems = validationSummary.find('.dx-validationsummary-item'); - await t.expect(updatedValidationSummaryItems.count).eql(1, 'Should have exactly one validation error in summary'); - const errorMessage = await updatedValidationSummaryItems.nth(0).innerText; - await t.expect(errorMessage).eql('Type is required', 'Error message should be "Type is required"'); - }, - - 'SelectBox validation should pass when value is selected', - async (t) => { - const validateButton = Selector('.dx-button-text').withText('Validate'); - const validationSummary = Selector('.dx-validationsummary'); - const selectBox = Selector('.dx-selectbox'); - const selectBoxArrow = selectBox.find('.dx-dropdowneditor-button'); - - await t.click(selectBoxArrow); - const firstItem = Selector('.dx-item').withText('One'); - - await t - .expect(firstItem.exists).ok('First item should exist in dropdown') - .click(firstItem); - - await t.click(validateButton); - - const updatedValidationSummaryItems = validationSummary.find('.dx-validationsummary-item'); - await t.expect(updatedValidationSummaryItems.count).eql(0, 'Should have no validation errors when value is selected'); - } -]); diff --git a/e2e/wrappers/tests/textbox.spec.ts b/e2e/wrappers/tests/textbox.spec.ts new file mode 100644 index 000000000000..26c84d61c172 --- /dev/null +++ b/e2e/wrappers/tests/textbox.spec.ts @@ -0,0 +1,27 @@ +import { expect, test } from '../fixtures'; + +test.describe('TextBox Dynamic Styles scenarios', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/examples/text-box-dynamic-styles'); + }); + + test('TextBox should update inline styles', async ({ page }) => { + const textboxWrapper = page.locator('.dx-textbox'); + const textboxInput = page.locator('.dx-texteditor-input'); + + await expect(textboxWrapper).toBeAttached(); + await expect(textboxInput).toBeAttached(); + + await textboxInput.pressSequentially('trigger'); + await textboxInput.press('Enter'); + + await expect(textboxInput).toHaveValue('trigger'); + await expect(textboxWrapper).toHaveCSS('background-color', 'rgb(255, 99, 132)'); + + await textboxInput.pressSequentially(' again'); + await textboxInput.press('Enter'); + + await expect(textboxInput).toHaveValue('trigger again'); + await expect(textboxWrapper).toHaveCSS('background-color', 'rgb(54, 162, 235)'); + }); +}); diff --git a/e2e/wrappers/tests/textbox.test.js b/e2e/wrappers/tests/textbox.test.js deleted file mode 100644 index 5dc2fcbdedfe..000000000000 --- a/e2e/wrappers/tests/textbox.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { Selector } from 'testcafe'; -import { testInFramework } from '../test-helpers'; - -testInFramework('TextBox Dynamic Styles scenarios', 'text-box-dynamic-styles', [ - 'TextBox should update inline styles', - async (t) => { - const textboxWrapper = Selector('.dx-textbox'); - const textboxInput = Selector('.dx-texteditor-input'); - await t.expect(textboxWrapper.exists).ok('TextBox Wrapper should exist'); - await t.expect(textboxInput.exists).ok('TextBox Input should exist'); - await t - .typeText(textboxInput, 'trigger') - .pressKey('enter') - .expect(textboxInput.value) - .eql('trigger') - .expect(textboxWrapper.getStyleProperty('background-color')) - .eql('rgb(255, 99, 132)') - .typeText(textboxInput, ' again') - .pressKey('enter') - .expect(textboxInput.value) - .eql('trigger again') - .expect(textboxWrapper.getStyleProperty('background-color')) - .eql('rgb(54, 162, 235)'); - }, -]); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6cab23141008..66e94d5547df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,6 +67,9 @@ catalogs: '@eslint/js': specifier: 9.39.4 version: 9.39.4 + '@playwright/test': + specifier: 1.62.1 + version: 1.62.1 '@stylistic/eslint-plugin': specifier: 5.10.0 version: 5.10.0 @@ -1306,6 +1309,9 @@ importers: '@eslint/js': specifier: 'catalog:' version: 9.39.4 + '@playwright/test': + specifier: 'catalog:' + version: 1.62.1 '@types/jasmine': specifier: 5.1.4 version: 5.1.4 @@ -1321,6 +1327,9 @@ importers: '@vitejs/plugin-vue': specifier: 5.2.4 version: 5.2.4(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.8.3)) + cross-env: + specifier: 7.0.3 + version: 7.0.3 eslint: specifier: 9.39.4 version: 9.39.4(jiti@2.6.1) @@ -1351,9 +1360,6 @@ importers: karma-jasmine-html-reporter: specifier: 2.1.0 version: 2.1.0(jasmine-core@5.6.0)(karma-jasmine@5.1.0(karma@6.4.4))(karma@6.4.4) - testcafe: - specifier: 'catalog:' - version: 3.7.5(supports-color@7.2.0) typescript: specifier: 5.8.3 version: 5.8.3 @@ -6583,6 +6589,11 @@ packages: resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} engines: {node: ^14.18.0 || >=16.0.0} + '@playwright/test@1.62.1': + resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==} + engines: {node: '>=20'} + hasBin: true + '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -11299,6 +11310,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -14377,6 +14393,16 @@ packages: resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} engines: {node: '>=16.0.0'} + playwright-core@1.62.1: + resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.62.1: + resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==} + engines: {node: '>=20'} + hasBin: true + plimit-lit@1.6.1: resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} engines: {node: '>=12'} @@ -23613,6 +23639,10 @@ snapshots: '@pkgr/core@0.3.6': {} + '@playwright/test@1.62.1': + dependencies: + playwright: 1.62.1 + '@popperjs/core@2.11.8': {} '@preact/signals-core@1.14.1': {} @@ -30036,6 +30066,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -34489,6 +34522,14 @@ snapshots: pvutils: 1.1.5 tslib: 2.8.1 + playwright-core@1.62.1: {} + + playwright@1.62.1: + dependencies: + playwright-core: 1.62.1 + optionalDependencies: + fsevents: 2.3.2 + plimit-lit@1.6.1: dependencies: queue-lit: 1.5.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0494ceb08b2d..7cc132bd4434 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -91,6 +91,7 @@ catalog: zod: 3.24.4 zod-to-json-schema: 3.24.6 "@babel/eslint-parser": 7.29.7 + "@playwright/test": 1.62.1 "@eslint-stylistic/metadata": ^2.13.0 "@eslint/eslintrc": 3.3.5 "@stylistic/eslint-plugin": 5.10.0 From 6213e22b286b819734c355f264ef90b284948108 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Wed, 12 Aug 2026 14:15:43 -0300 Subject: [PATCH 02/22] Address review: validate port, read CI versions in docker script, docs and test title fixes --- e2e/wrappers/README.md | 2 +- e2e/wrappers/docker/Dockerfile | 7 +++---- e2e/wrappers/docker/run.sh | 21 ++++++++++++++++--- e2e/wrappers/serve.js | 5 +++++ .../tests/inputs-list-in-form.spec.ts | 2 +- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/e2e/wrappers/README.md b/e2e/wrappers/README.md index 3e2de13330db..6f819e956485 100644 --- a/e2e/wrappers/README.md +++ b/e2e/wrappers/README.md @@ -19,7 +19,7 @@ The tests are written with [Playwright](https://playwright.dev/) and use the sys ## Run locally ```bash -pnpm install +pnpm install --frozen-lockfile pnpm nx all:build-testing workflows cd e2e/wrappers diff --git a/e2e/wrappers/docker/Dockerfile b/e2e/wrappers/docker/Dockerfile index bcde5274689c..7ca3317cfc3a 100644 --- a/e2e/wrappers/docker/Dockerfile +++ b/e2e/wrappers/docker/Dockerfile @@ -1,8 +1,7 @@ -# Reproduces the "Wrappers E2E Tests" CI environment: Node from .node-version -# and Google Chrome pinned to the version used by .github/actions/setup-chrome. -FROM node:24.16.0-bookworm-slim +ARG NODE_IMAGE=node:lts-bookworm-slim +FROM ${NODE_IMAGE} -ARG CHROME_VERSION=150.0.7871.181 +ARG CHROME_VERSION RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates curl \ diff --git a/e2e/wrappers/docker/run.sh b/e2e/wrappers/docker/run.sh index 8e080ac76a51..f74e834b879e 100755 --- a/e2e/wrappers/docker/run.sh +++ b/e2e/wrappers/docker/run.sh @@ -1,9 +1,10 @@ #!/bin/bash -# Runs the wrappers e2e tests in a container that matches the CI environment. +# Runs the wrappers e2e tests in a container that matches the CI environment: Node and Google +# Chrome are taken from .node-version and from the workflow that runs these tests on CI. # The repository is mounted as is, so dependencies must be installed and # the app under test built on the host first: # -# pnpm install +# pnpm install --frozen-lockfile # pnpm nx all:build-testing workflows # cd e2e/wrappers && pnpm run build:react19 # @@ -16,11 +17,25 @@ shift || true DOCKER_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$DOCKER_DIR/../../.." && pwd)" +WORKFLOW="$REPO_ROOT/.github/workflows/wrapper_tests_e2e.yml" + +NODE_VERSION="$(cat "$REPO_ROOT/.node-version")" +CHROME_VERSION="$(grep -m1 'chrome-version:' "$WORKFLOW" | cut -d: -f2 | tr -d " '\"")" + +if [ -z "$NODE_VERSION" ] || [ -z "$CHROME_VERSION" ]; then + echo "❌ Cannot read the Node version from .node-version or the Chrome version from $WORKFLOW." >&2 + exit 1 +fi + +echo "Node $NODE_VERSION, Google Chrome $CHROME_VERSION" # Google Chrome for Linux ships for amd64 only, so the platform is pinned as on CI. PLATFORM=linux/amd64 -docker build --platform "$PLATFORM" -t devextreme-wrappers-e2e "$DOCKER_DIR" +docker build --platform "$PLATFORM" \ + --build-arg "NODE_IMAGE=node:$NODE_VERSION-bookworm-slim" \ + --build-arg "CHROME_VERSION=$CHROME_VERSION" \ + -t devextreme-wrappers-e2e "$DOCKER_DIR" docker run --rm --ipc=host --platform "$PLATFORM" \ -v "$REPO_ROOT:/repo" \ diff --git a/e2e/wrappers/serve.js b/e2e/wrappers/serve.js index 37b8ca11cab6..43caca16ee89 100644 --- a/e2e/wrappers/serve.js +++ b/e2e/wrappers/serve.js @@ -20,6 +20,11 @@ if(!distDir) { process.exit(1); } +if(!Number.isInteger(port) || port <= 0) { + console.error(`❌ Invalid port: ${argv.port}. Pass a positive integer, for example --port=3030.`); + process.exit(1); +} + const root = path.resolve(__dirname, distDir); const indexPath = path.join(root, 'index.html'); diff --git a/e2e/wrappers/tests/inputs-list-in-form.spec.ts b/e2e/wrappers/tests/inputs-list-in-form.spec.ts index 430b228820e7..71f6a1e6cf7e 100644 --- a/e2e/wrappers/tests/inputs-list-in-form.spec.ts +++ b/e2e/wrappers/tests/inputs-list-in-form.spec.ts @@ -5,7 +5,7 @@ test.describe('inputs-list-in-form scenarios', () => { await page.goto('/examples/inputs-list-in-form'); }); - test('Phone inputs should adding and deleting correctly', async ({ page }) => { + test('Phone inputs should be added and deleted correctly', async ({ page }) => { const addButton = page.locator('.dx-button-text', { hasText: 'Add phone' }); const deleteButton = page.locator('.dx-button', { has: page.locator('.dx-icon-trash') }); const phoneGroup = page.locator('[aria-labelledby$="_phones-container"]'); From 8c224ababb6b727801cc7243679895ce672757cd Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Thu, 13 Aug 2026 10:58:36 -0300 Subject: [PATCH 03/22] Keep Chrome scrollbars visible and align the local container with the CI environment --- e2e/wrappers/README.md | 33 +++++++++++++++++++++++++++++-- e2e/wrappers/docker/Dockerfile | 18 +++++++++++------ e2e/wrappers/docker/run.sh | 5 +++-- e2e/wrappers/playwright.config.ts | 7 ++++++- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/e2e/wrappers/README.md b/e2e/wrappers/README.md index 6f819e956485..e62fb0ef1a70 100644 --- a/e2e/wrappers/README.md +++ b/e2e/wrappers/README.md @@ -35,10 +35,39 @@ Examples that exist for a single framework only (Chat, Gantt) are skipped in the ## Run in the CI environment -The container pins Node and Google Chrome to the versions used by the `Wrappers E2E Tests` -workflow. Dependencies and application builds are taken from the host: +The container mirrors the OS, Node and Google Chrome the tests get on CI. Dependencies and +application builds are taken from the host: ```bash cd e2e/wrappers docker/run.sh react19 ``` + +To drive the run from the browser on your machine, start the UI mode inside the container and +open `http://localhost:9323`: + +```bash +docker run --rm --platform linux/amd64 --shm-size=2gb -p 9323:9323 \ + -v "$(git rev-parse --show-toplevel):/repo" -w /repo/e2e/wrappers -e FRAMEWORK=react19 \ + devextreme-wrappers-e2e \ + node_modules/.bin/playwright test --ui-host=0.0.0.0 --ui-port=9323 +``` + +The repository is mounted, so edits made on the host are picked up and the watch mode re-runs +the affected tests. Plain `--ui` is not used on purpose: it opens the interface in the bundled +Chromium, which is deliberately not installed. + +Artifacts from a failed CI run are read with the same tooling: + +```bash +pnpm exec playwright show-report /playwright-report +pnpm exec playwright show-trace /test-results//trace.zip --port 0 +``` + +## Rendering + +The default Chrome scrollbars must stay visible: they take layout space, so a page rendered +without them differs from what a user sees and from the etalons the TestCafe tests produce. +Playwright hides scrollbars in headless mode by default, so `--hide-scrollbars` is removed in +`playwright.config.ts`. Measured in the CI-like container, the scrollbar is 15px wide both here +and in TestCafe. diff --git a/e2e/wrappers/docker/Dockerfile b/e2e/wrappers/docker/Dockerfile index 7ca3317cfc3a..5b8a844edcd2 100644 --- a/e2e/wrappers/docker/Dockerfile +++ b/e2e/wrappers/docker/Dockerfile @@ -1,13 +1,19 @@ -ARG NODE_IMAGE=node:lts-bookworm-slim -FROM ${NODE_IMAGE} +# Mirrors the environment the tests get on CI: Ubuntu 24.04, Node from .node-version and +# Google Chrome pinned to the version the workflow installs. Keep the font packages in sync +# with the CI runner image — they affect text rendering and, later, screenshot comparison. +FROM ubuntu:24.04 +ARG NODE_VERSION ARG CHROME_VERSION -RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates curl \ +RUN echo 'APT::Install-Recommends "0";' > /etc/apt/apt.conf.d/dx-no-recommends \ + && apt-get update \ + && apt-get install -y ca-certificates curl fonts-noto-color-emoji \ && curl -fsSL "https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}-1_amd64.deb" -o /tmp/chrome.deb \ - && apt-get install -y --no-install-recommends /tmp/chrome.deb \ - && rm /tmp/chrome.deb \ + && apt-get install -y /tmp/chrome.deb \ + && curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" -o /tmp/node.tar.gz \ + && tar -xzf /tmp/node.tar.gz -C /usr/local --strip-components=1 \ + && rm /tmp/chrome.deb /tmp/node.tar.gz \ && rm -rf /var/lib/apt/lists/* WORKDIR /repo/e2e/wrappers diff --git a/e2e/wrappers/docker/run.sh b/e2e/wrappers/docker/run.sh index f74e834b879e..426fbcb76cc3 100755 --- a/e2e/wrappers/docker/run.sh +++ b/e2e/wrappers/docker/run.sh @@ -33,11 +33,12 @@ echo "Node $NODE_VERSION, Google Chrome $CHROME_VERSION" PLATFORM=linux/amd64 docker build --platform "$PLATFORM" \ - --build-arg "NODE_IMAGE=node:$NODE_VERSION-bookworm-slim" \ + --build-arg "NODE_VERSION=$NODE_VERSION" \ --build-arg "CHROME_VERSION=$CHROME_VERSION" \ -t devextreme-wrappers-e2e "$DOCKER_DIR" -docker run --rm --ipc=host --platform "$PLATFORM" \ +# Shared memory and seccomp are set as the CI runner container gets them. +docker run --rm --platform "$PLATFORM" --shm-size=2gb --security-opt seccomp=unconfined \ -v "$REPO_ROOT:/repo" \ -w /repo/e2e/wrappers \ -e CI=true \ diff --git a/e2e/wrappers/playwright.config.ts b/e2e/wrappers/playwright.config.ts index dc115d9d7962..4acd82a80950 100644 --- a/e2e/wrappers/playwright.config.ts +++ b/e2e/wrappers/playwright.config.ts @@ -33,7 +33,12 @@ export default defineConfig({ channel: 'chrome', headless: true, viewport: { width: 1200, height: 800 }, - launchOptions: { args: ['--no-sandbox', '--disable-gpu'] }, + launchOptions: { + args: ['--no-sandbox', '--disable-gpu'], + // Playwright hides scrollbars in headless by default, so the page gets no scrollbar + // gutter and its layout differs from a real browser and from the TestCafe etalons. + ignoreDefaultArgs: ['--hide-scrollbars'], + }, trace: 'retain-on-failure', screenshot: 'only-on-failure', }, From 06324205f2f204991417335cc7616605928f88ea Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Sun, 16 Aug 2026 13:51:35 -0300 Subject: [PATCH 04/22] Upload the Playwright report and traces as separate CI artifacts, add the UI mode to the docker script --- .github/workflows/wrapper_tests_e2e.yml | 18 +++++++--- e2e/wrappers/README.md | 29 +++++++++------ e2e/wrappers/docker/run.sh | 47 ++++++++++++++++++------- 3 files changed, 66 insertions(+), 28 deletions(-) diff --git a/.github/workflows/wrapper_tests_e2e.yml b/.github/workflows/wrapper_tests_e2e.yml index 336fedc81790..c39e9440a83a 100644 --- a/.github/workflows/wrapper_tests_e2e.yml +++ b/.github/workflows/wrapper_tests_e2e.yml @@ -174,12 +174,20 @@ jobs: working-directory: e2e/wrappers run: pnpm run test:${{ matrix.framework }} - - name: Upload test artifacts on failure + # The report and the traces are uploaded separately: each artifact then unpacks into the + # layout "playwright show-report" and "playwright show-trace" expect. + - name: Upload test report on failure if: ${{ failure() }} uses: actions/upload-artifact@v7 with: - name: test-fails-${{matrix.framework}} - path: | - e2e/wrappers/playwright-report - e2e/wrappers/test-results + name: playwright-report-${{matrix.framework}} + path: e2e/wrappers/playwright-report + if-no-files-found: ignore + + - name: Upload test traces on failure + if: ${{ failure() }} + uses: actions/upload-artifact@v7 + with: + name: playwright-traces-${{matrix.framework}} + path: e2e/wrappers/test-results if-no-files-found: ignore diff --git a/e2e/wrappers/README.md b/e2e/wrappers/README.md index e62fb0ef1a70..734d4a0e75d8 100644 --- a/e2e/wrappers/README.md +++ b/e2e/wrappers/README.md @@ -43,27 +43,34 @@ cd e2e/wrappers docker/run.sh react19 ``` -To drive the run from the browser on your machine, start the UI mode inside the container and -open `http://localhost:9323`: +The browser runs in the container while you watch and re-run the tests from the browser on your +machine — start the UI mode and open `http://localhost:9323`: ```bash -docker run --rm --platform linux/amd64 --shm-size=2gb -p 9323:9323 \ - -v "$(git rev-parse --show-toplevel):/repo" -w /repo/e2e/wrappers -e FRAMEWORK=react19 \ - devextreme-wrappers-e2e \ - node_modules/.bin/playwright test --ui-host=0.0.0.0 --ui-port=9323 +docker/run.sh --ui react19 ``` The repository is mounted, so edits made on the host are picked up and the watch mode re-runs -the affected tests. Plain `--ui` is not used on purpose: it opens the interface in the bundled -Chromium, which is deliberately not installed. +the affected tests. Plain `playwright test --ui` is not used on purpose: it opens the interface +in the bundled Chromium, which is deliberately not installed. -Artifacts from a failed CI run are read with the same tooling: +## Investigate a failed CI run + +The `Wrappers E2E Tests` workflow uploads two artifacts per framework when tests fail: the HTML +report and the traces. Download them and open with the tools Playwright ships: ```bash -pnpm exec playwright show-report /playwright-report -pnpm exec playwright show-trace /test-results//trace.zip --port 0 +gh run download -n playwright-report-react19 -D ci-report +gh run download -n playwright-traces-react19 -D ci-traces + +pnpm exec playwright show-report ci-report +pnpm exec playwright show-trace ci-traces//trace.zip --port 0 ``` +The trace is a step-by-step timeline: the DOM before and after every action, network, console +output and the error, so a failure that only happens on CI can be inspected without reproducing +it. To reproduce it anyway, run the same test in the container — the environment matches. + ## Rendering The default Chrome scrollbars must stay visible: they take layout space, so a page rendered diff --git a/e2e/wrappers/docker/run.sh b/e2e/wrappers/docker/run.sh index 426fbcb76cc3..f4befae6f160 100755 --- a/e2e/wrappers/docker/run.sh +++ b/e2e/wrappers/docker/run.sh @@ -1,17 +1,26 @@ #!/bin/bash -# Runs the wrappers e2e tests in a container that matches the CI environment: Node and Google -# Chrome are taken from .node-version and from the workflow that runs these tests on CI. -# The repository is mounted as is, so dependencies must be installed and -# the app under test built on the host first: +# Runs the wrappers e2e tests in a container that matches the CI environment: the OS, Node and +# Google Chrome are the ones the tests get on CI. The repository is mounted as is, so +# dependencies must be installed and the app under test built on the host first: # # pnpm install --frozen-lockfile # pnpm nx all:build-testing workflows # cd e2e/wrappers && pnpm run build:react19 # -# Usage: docker/run.sh [react19|vue3|angular] [extra playwright args] +# Usage: +# docker/run.sh [react19|vue3|angular] [extra playwright args] +# docker/run.sh --ui [react19|vue3|angular] open the UI mode at http://localhost:9323 set -euo pipefail +UI_PORT=9323 +UI=false + +if [ "${1:-}" = "--ui" ]; then + UI=true + shift +fi + FRAMEWORK="${1:-react19}" shift || true @@ -38,10 +47,24 @@ docker build --platform "$PLATFORM" \ -t devextreme-wrappers-e2e "$DOCKER_DIR" # Shared memory and seccomp are set as the CI runner container gets them. -docker run --rm --platform "$PLATFORM" --shm-size=2gb --security-opt seccomp=unconfined \ - -v "$REPO_ROOT:/repo" \ - -w /repo/e2e/wrappers \ - -e CI=true \ - -e "FRAMEWORK=$FRAMEWORK" \ - devextreme-wrappers-e2e \ - node_modules/.bin/playwright test "$@" +DOCKER_ARGS=( + --rm + --platform "$PLATFORM" + --shm-size=2gb + --security-opt seccomp=unconfined + -v "$REPO_ROOT:/repo" + -w /repo/e2e/wrappers + -e "FRAMEWORK=$FRAMEWORK" +) +PLAYWRIGHT_ARGS=(test) + +if [ "$UI" = true ]; then + DOCKER_ARGS+=(-p "$UI_PORT:$UI_PORT") + PLAYWRIGHT_ARGS+=(--ui-host=0.0.0.0 "--ui-port=$UI_PORT") + echo "UI mode for $FRAMEWORK: open http://localhost:$UI_PORT" +else + DOCKER_ARGS+=(-e CI=true) + PLAYWRIGHT_ARGS+=("$@") +fi + +docker run "${DOCKER_ARGS[@]}" devextreme-wrappers-e2e node_modules/.bin/playwright "${PLAYWRIGHT_ARGS[@]}" From 8734d8821859e0f8e25e4c05125655a31631e95f Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Tue, 25 Aug 2026 07:28:12 -0300 Subject: [PATCH 05/22] Shorten the artifact upload comment to the reason only --- .github/workflows/wrapper_tests_e2e.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/wrapper_tests_e2e.yml b/.github/workflows/wrapper_tests_e2e.yml index c39e9440a83a..acce47596123 100644 --- a/.github/workflows/wrapper_tests_e2e.yml +++ b/.github/workflows/wrapper_tests_e2e.yml @@ -174,8 +174,7 @@ jobs: working-directory: e2e/wrappers run: pnpm run test:${{ matrix.framework }} - # The report and the traces are uploaded separately: each artifact then unpacks into the - # layout "playwright show-report" and "playwright show-trace" expect. + # Kept as two artifacts: merged into one, the report no longer opens from the download. - name: Upload test report on failure if: ${{ failure() }} uses: actions/upload-artifact@v7 From ca516221c5a1e8d775cc8b41269f8010f094f650 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Tue, 25 Aug 2026 07:33:06 -0300 Subject: [PATCH 06/22] Drop the artifact upload comment --- .github/workflows/wrapper_tests_e2e.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/wrapper_tests_e2e.yml b/.github/workflows/wrapper_tests_e2e.yml index acce47596123..d647c75b75f0 100644 --- a/.github/workflows/wrapper_tests_e2e.yml +++ b/.github/workflows/wrapper_tests_e2e.yml @@ -174,7 +174,6 @@ jobs: working-directory: e2e/wrappers run: pnpm run test:${{ matrix.framework }} - # Kept as two artifacts: merged into one, the report no longer opens from the download. - name: Upload test report on failure if: ${{ failure() }} uses: actions/upload-artifact@v7 From 15af875ee848fea31f7947a592f2a43cbc1c3016 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Thu, 27 Aug 2026 09:54:55 -0300 Subject: [PATCH 07/22] Rewrite the wrappers static server as an ES module and drop minimist --- e2e/wrappers/.gitignore | 2 ++ e2e/wrappers/docker/Dockerfile | 4 +--- e2e/wrappers/package.json | 1 - e2e/wrappers/playwright.config.ts | 2 +- e2e/wrappers/{serve.js => serve.mjs} | 26 ++++++++++++++++---------- pnpm-lock.yaml | 3 --- 6 files changed, 20 insertions(+), 18 deletions(-) rename e2e/wrappers/{serve.js => serve.mjs} (61%) diff --git a/e2e/wrappers/.gitignore b/e2e/wrappers/.gitignore index 5c4ffa21fa5e..8d5a8b9a498e 100644 --- a/e2e/wrappers/.gitignore +++ b/e2e/wrappers/.gitignore @@ -1,2 +1,4 @@ playwright-report/ test-results/ +ci-report/ +ci-traces/ diff --git a/e2e/wrappers/docker/Dockerfile b/e2e/wrappers/docker/Dockerfile index 5b8a844edcd2..c2b440c8f26b 100644 --- a/e2e/wrappers/docker/Dockerfile +++ b/e2e/wrappers/docker/Dockerfile @@ -1,6 +1,4 @@ -# Mirrors the environment the tests get on CI: Ubuntu 24.04, Node from .node-version and -# Google Chrome pinned to the version the workflow installs. Keep the font packages in sync -# with the CI runner image — they affect text rendering and, later, screenshot comparison. +# Mirrors the environment the tests get on CI. FROM ubuntu:24.04 ARG NODE_VERSION diff --git a/e2e/wrappers/package.json b/e2e/wrappers/package.json index e8f3b42f52b3..81e9a5b89486 100644 --- a/e2e/wrappers/package.json +++ b/e2e/wrappers/package.json @@ -40,7 +40,6 @@ "inferno-create-element": "^8.2.0", "jszip": "^3.10.1", "luxon": "3.4.4", - "minimist": "1.2.8", "react": "^19.2.8", "react-dom": "^19.2.8", "react-router": "^8.3.0", diff --git a/e2e/wrappers/playwright.config.ts b/e2e/wrappers/playwright.config.ts index 4acd82a80950..d1a19573546b 100644 --- a/e2e/wrappers/playwright.config.ts +++ b/e2e/wrappers/playwright.config.ts @@ -43,7 +43,7 @@ export default defineConfig({ screenshot: 'only-on-failure', }, webServer: { - command: `node ./serve.js --framework=${framework} --port=${port}`, + command: `node ./serve.mjs --framework=${framework} --port=${port}`, url: baseURL, reuseExistingServer: !process.env.CI, timeout: 30000, diff --git a/e2e/wrappers/serve.js b/e2e/wrappers/serve.mjs similarity index 61% rename from e2e/wrappers/serve.js rename to e2e/wrappers/serve.mjs index 43caca16ee89..7949d8078f9d 100644 --- a/e2e/wrappers/serve.js +++ b/e2e/wrappers/serve.mjs @@ -1,8 +1,8 @@ -const fs = require('fs'); -const path = require('path'); -const process = require('process'); -const minimist = require('minimist'); -const express = require('express'); +import fs from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; +import { parseArgs } from 'node:util'; +import express from 'express'; const DIST_DIRS = { react19: 'builders/react19/dist', @@ -10,9 +10,15 @@ const DIST_DIRS = { vue3: 'builders/vue3/dist', }; -const argv = minimist(process.argv.slice(2)); -const framework = argv.framework; -const port = Number(argv.port); +const { values } = parseArgs({ + options: { + framework: { type: 'string' }, + port: { type: 'string' }, + }, +}); + +const framework = values.framework; +const port = Number(values.port); const distDir = DIST_DIRS[framework]; if(!distDir) { @@ -21,11 +27,11 @@ if(!distDir) { } if(!Number.isInteger(port) || port <= 0) { - console.error(`❌ Invalid port: ${argv.port}. Pass a positive integer, for example --port=3030.`); + console.error(`❌ Invalid port: ${values.port}. Pass a positive integer, for example --port=3030.`); process.exit(1); } -const root = path.resolve(__dirname, distDir); +const root = path.resolve(import.meta.dirname, distDir); const indexPath = path.join(root, 'index.html'); if(!fs.existsSync(indexPath)) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66e94d5547df..57cb806b54ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1257,9 +1257,6 @@ importers: luxon: specifier: 3.4.4 version: 3.4.4 - minimist: - specifier: 1.2.8 - version: 1.2.8 react: specifier: ^19.2.8 version: 19.2.8 From e62c4075ddefcd367939efbf6ce9e19f5d1c40ad Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Thu, 27 Aug 2026 10:02:47 -0300 Subject: [PATCH 08/22] Trim the wrappers README to the three things it needs to answer --- e2e/wrappers/README.md | 57 ++++++++---------------------------------- 1 file changed, 11 insertions(+), 46 deletions(-) diff --git a/e2e/wrappers/README.md b/e2e/wrappers/README.md index 734d4a0e75d8..6e92bc6525b3 100644 --- a/e2e/wrappers/README.md +++ b/e2e/wrappers/README.md @@ -1,21 +1,9 @@ # Wrappers E2E tests End-to-end tests that check DevExtreme components inside the React, Vue and Angular wrappers. -The tests are written with [Playwright](https://playwright.dev/) and use the system Google Chrome +They run on [Playwright](https://playwright.dev/) and use the system Google Chrome (`channel: 'chrome'`), so no browser download is needed. -## Layout - -| Path | Purpose | -|------------------------|----------------------------------------------------------------------| -| `builders/*` | Host applications that render the examples for each framework | -| `examples/*` | Examples under test, one folder per scenario and framework | -| `tests/*.spec.ts` | Playwright specs | -| `fixtures.ts` | `test`/`expect` with the `framework` option added | -| `playwright.config.ts` | Per-framework port, base URL and dev server | -| `serve.js` | Static server for a built application | -| `docker/` | Container that matches the CI environment | - ## Run locally ```bash @@ -24,40 +12,26 @@ pnpm nx all:build-testing workflows cd e2e/wrappers pnpm run build:react19 # or build:vue3 / build:angular / build:all - pnpm run test:react19 # or test:vue3 / test:angular ``` -`playwright test` starts `serve.js` on its own, so no separate server is needed. -Useful flags: `--headed`, `--debug`, `--ui`, `--reporter=html`. - -Examples that exist for a single framework only (Chat, Gantt) are skipped in the other frameworks. +`playwright test` starts the server for the built application itself. The Chat and Gantt examples +exist for React only, so they are reported as skipped for the other frameworks. ## Run in the CI environment -The container mirrors the OS, Node and Google Chrome the tests get on CI. Dependencies and -application builds are taken from the host: +The container takes Node from `.node-version` and Chrome from the version the workflow installs; +dependencies and application builds come from the host. ```bash -cd e2e/wrappers docker/run.sh react19 +docker/run.sh --ui react19 # then open http://localhost:9323 ``` -The browser runs in the container while you watch and re-run the tests from the browser on your -machine — start the UI mode and open `http://localhost:9323`: - -```bash -docker/run.sh --ui react19 -``` - -The repository is mounted, so edits made on the host are picked up and the watch mode re-runs -the affected tests. Plain `playwright test --ui` is not used on purpose: it opens the interface -in the bundled Chromium, which is deliberately not installed. +Open the UI on `localhost` — the address Playwright prints, `0.0.0.0`, is not a secure origin and +its trace viewer will not load there. -## Investigate a failed CI run - -The `Wrappers E2E Tests` workflow uploads two artifacts per framework when tests fail: the HTML -report and the traces. Download them and open with the tools Playwright ships: +## Read a failed CI run ```bash gh run download -n playwright-report-react19 -D ci-report @@ -67,14 +41,5 @@ pnpm exec playwright show-report ci-report pnpm exec playwright show-trace ci-traces//trace.zip --port 0 ``` -The trace is a step-by-step timeline: the DOM before and after every action, network, console -output and the error, so a failure that only happens on CI can be inspected without reproducing -it. To reproduce it anyway, run the same test in the container — the environment matches. - -## Rendering - -The default Chrome scrollbars must stay visible: they take layout space, so a page rendered -without them differs from what a user sees and from the etalons the TestCafe tests produce. -Playwright hides scrollbars in headless mode by default, so `--hide-scrollbars` is removed in -`playwright.config.ts`. Measured in the CI-like container, the scrollbar is 15px wide both here -and in TestCafe. +The trace holds every action with the DOM before and after it, the network and the console, so a +failure that only happens on CI can be diagnosed without reproducing it. From f827903fd2a33fb04c212901c40e2ef106af93bd Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Thu, 27 Aug 2026 10:05:40 -0300 Subject: [PATCH 09/22] Trim the docker script to its usage line and drop redundant guards --- e2e/wrappers/README.md | 3 +-- e2e/wrappers/docker/run.sh | 53 +++++++------------------------------- 2 files changed, 11 insertions(+), 45 deletions(-) diff --git a/e2e/wrappers/README.md b/e2e/wrappers/README.md index 6e92bc6525b3..8916e6d6db8b 100644 --- a/e2e/wrappers/README.md +++ b/e2e/wrappers/README.md @@ -15,8 +15,7 @@ pnpm run build:react19 # or build:vue3 / build:angular / build:all pnpm run test:react19 # or test:vue3 / test:angular ``` -`playwright test` starts the server for the built application itself. The Chat and Gantt examples -exist for React only, so they are reported as skipped for the other frameworks. +`playwright test` starts the server for the built application itself. ## Run in the CI environment diff --git a/e2e/wrappers/docker/run.sh b/e2e/wrappers/docker/run.sh index f4befae6f160..0cd917bc92fb 100755 --- a/e2e/wrappers/docker/run.sh +++ b/e2e/wrappers/docker/run.sh @@ -1,15 +1,5 @@ #!/bin/bash -# Runs the wrappers e2e tests in a container that matches the CI environment: the OS, Node and -# Google Chrome are the ones the tests get on CI. The repository is mounted as is, so -# dependencies must be installed and the app under test built on the host first: -# -# pnpm install --frozen-lockfile -# pnpm nx all:build-testing workflows -# cd e2e/wrappers && pnpm run build:react19 -# -# Usage: -# docker/run.sh [react19|vue3|angular] [extra playwright args] -# docker/run.sh --ui [react19|vue3|angular] open the UI mode at http://localhost:9323 +# Usage: docker/run.sh [--ui] [react19|vue3|angular] [playwright args] set -euo pipefail @@ -24,47 +14,24 @@ fi FRAMEWORK="${1:-react19}" shift || true -DOCKER_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$DOCKER_DIR/../../.." && pwd)" -WORKFLOW="$REPO_ROOT/.github/workflows/wrapper_tests_e2e.yml" - +REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" NODE_VERSION="$(cat "$REPO_ROOT/.node-version")" -CHROME_VERSION="$(grep -m1 'chrome-version:' "$WORKFLOW" | cut -d: -f2 | tr -d " '\"")" - -if [ -z "$NODE_VERSION" ] || [ -z "$CHROME_VERSION" ]; then - echo "❌ Cannot read the Node version from .node-version or the Chrome version from $WORKFLOW." >&2 - exit 1 -fi +CHROME_VERSION="$(grep -m1 'chrome-version:' "$REPO_ROOT/.github/workflows/wrapper_tests_e2e.yml" | cut -d: -f2 | tr -d " '\"")" echo "Node $NODE_VERSION, Google Chrome $CHROME_VERSION" -# Google Chrome for Linux ships for amd64 only, so the platform is pinned as on CI. -PLATFORM=linux/amd64 - -docker build --platform "$PLATFORM" \ +docker build --platform linux/amd64 \ --build-arg "NODE_VERSION=$NODE_VERSION" \ --build-arg "CHROME_VERSION=$CHROME_VERSION" \ - -t devextreme-wrappers-e2e "$DOCKER_DIR" + -t devextreme-wrappers-e2e "$REPO_ROOT/e2e/wrappers/docker" -# Shared memory and seccomp are set as the CI runner container gets them. -DOCKER_ARGS=( - --rm - --platform "$PLATFORM" - --shm-size=2gb - --security-opt seccomp=unconfined - -v "$REPO_ROOT:/repo" - -w /repo/e2e/wrappers - -e "FRAMEWORK=$FRAMEWORK" -) -PLAYWRIGHT_ARGS=(test) +RUN=(docker run --rm --platform linux/amd64 --shm-size=2gb --security-opt seccomp=unconfined + -v "$REPO_ROOT:/repo" -w /repo/e2e/wrappers -e "FRAMEWORK=$FRAMEWORK") if [ "$UI" = true ]; then - DOCKER_ARGS+=(-p "$UI_PORT:$UI_PORT") - PLAYWRIGHT_ARGS+=(--ui-host=0.0.0.0 "--ui-port=$UI_PORT") echo "UI mode for $FRAMEWORK: open http://localhost:$UI_PORT" -else - DOCKER_ARGS+=(-e CI=true) - PLAYWRIGHT_ARGS+=("$@") + exec "${RUN[@]}" -p "$UI_PORT:$UI_PORT" devextreme-wrappers-e2e \ + node_modules/.bin/playwright test --ui-host=0.0.0.0 --ui-port="$UI_PORT" fi -docker run "${DOCKER_ARGS[@]}" devextreme-wrappers-e2e node_modules/.bin/playwright "${PLAYWRIGHT_ARGS[@]}" +exec "${RUN[@]}" -e CI=true devextreme-wrappers-e2e node_modules/.bin/playwright test "$@" From ff9c1468dbbeb813fd8a4055117ab357cce6d236 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Thu, 27 Aug 2026 10:28:12 -0300 Subject: [PATCH 10/22] Forward playwright arguments in the docker UI mode --- e2e/wrappers/docker/run.sh | 2 +- e2e/wrappers/playwright.config.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/e2e/wrappers/docker/run.sh b/e2e/wrappers/docker/run.sh index 0cd917bc92fb..d8b76f397015 100755 --- a/e2e/wrappers/docker/run.sh +++ b/e2e/wrappers/docker/run.sh @@ -31,7 +31,7 @@ RUN=(docker run --rm --platform linux/amd64 --shm-size=2gb --security-opt seccom if [ "$UI" = true ]; then echo "UI mode for $FRAMEWORK: open http://localhost:$UI_PORT" exec "${RUN[@]}" -p "$UI_PORT:$UI_PORT" devextreme-wrappers-e2e \ - node_modules/.bin/playwright test --ui-host=0.0.0.0 --ui-port="$UI_PORT" + node_modules/.bin/playwright test --ui-host=0.0.0.0 --ui-port="$UI_PORT" "$@" fi exec "${RUN[@]}" -e CI=true devextreme-wrappers-e2e node_modules/.bin/playwright test "$@" diff --git a/e2e/wrappers/playwright.config.ts b/e2e/wrappers/playwright.config.ts index d1a19573546b..738b4b2183a8 100644 --- a/e2e/wrappers/playwright.config.ts +++ b/e2e/wrappers/playwright.config.ts @@ -35,8 +35,7 @@ export default defineConfig({ viewport: { width: 1200, height: 800 }, launchOptions: { args: ['--no-sandbox', '--disable-gpu'], - // Playwright hides scrollbars in headless by default, so the page gets no scrollbar - // gutter and its layout differs from a real browser and from the TestCafe etalons. + // Playwright hides scrollbars in headless by default, which changes the page layout. ignoreDefaultArgs: ['--hide-scrollbars'], }, trace: 'retain-on-failure', From 8b908e3918f7831296e0fbd1683ee5e06d91d44e Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Thu, 27 Aug 2026 13:34:22 -0300 Subject: [PATCH 11/22] Add the e2e/playwright package with the component test infrastructure --- .../workflows/testcafe_tests_playwright.yml | 321 ++++++++++++++++++ e2e/playwright/.gitignore | 1 + e2e/playwright/README.md | 100 ++++++ e2e/playwright/docker/Dockerfile | 17 + e2e/playwright/docker/run.sh | 33 ++ e2e/playwright/eslint.config.mjs | 79 +++++ e2e/playwright/fixtures.ts | 35 ++ e2e/playwright/helpers/apiMock.ts | 23 ++ e2e/playwright/helpers/const.ts | 9 + e2e/playwright/helpers/createWidget.ts | 32 ++ e2e/playwright/helpers/domUtils.ts | 126 +++++++ e2e/playwright/helpers/mockDate.ts | 10 + e2e/playwright/helpers/screenshots.ts | 74 ++++ e2e/playwright/helpers/testPageUtils.ts | 82 +++++ e2e/playwright/helpers/themeUtils.ts | 15 + e2e/playwright/models/button.ts | 24 ++ e2e/playwright/models/checkBox.ts | 23 ++ e2e/playwright/models/internal/widget.ts | 85 +++++ e2e/playwright/models/types.ts | 80 +++++ e2e/playwright/package.json | 24 ++ e2e/playwright/playwright.config.ts | 73 ++++ e2e/playwright/project.json | 34 ++ e2e/playwright/runner.mjs | 63 ++++ e2e/playwright/serve.mjs | 61 ++++ e2e/playwright/tests/container.html | 34 ++ .../tests/editors/checkBox/common.spec.ts | 24 ++ .../tests/navigation/button/common.spec.ts | 71 ++++ ...lingMode=contained (fluent.blue.light).png | Bin 0 -> 36994 bytes ...ylingMode=outlined (fluent.blue.light).png | Bin 0 -> 37094 bytes ..., stylingMode=text (fluent.blue.light).png | Bin 0 -> 33951 bytes e2e/playwright/tsconfig.json | 21 ++ pnpm-lock.yaml | 45 +++ 32 files changed, 1619 insertions(+) create mode 100644 .github/workflows/testcafe_tests_playwright.yml create mode 100644 e2e/playwright/.gitignore create mode 100644 e2e/playwright/README.md create mode 100644 e2e/playwright/docker/Dockerfile create mode 100755 e2e/playwright/docker/run.sh create mode 100644 e2e/playwright/eslint.config.mjs create mode 100644 e2e/playwright/fixtures.ts create mode 100644 e2e/playwright/helpers/apiMock.ts create mode 100644 e2e/playwright/helpers/const.ts create mode 100644 e2e/playwright/helpers/createWidget.ts create mode 100644 e2e/playwright/helpers/domUtils.ts create mode 100644 e2e/playwright/helpers/mockDate.ts create mode 100644 e2e/playwright/helpers/screenshots.ts create mode 100644 e2e/playwright/helpers/testPageUtils.ts create mode 100644 e2e/playwright/helpers/themeUtils.ts create mode 100644 e2e/playwright/models/button.ts create mode 100644 e2e/playwright/models/checkBox.ts create mode 100644 e2e/playwright/models/internal/widget.ts create mode 100644 e2e/playwright/models/types.ts create mode 100644 e2e/playwright/package.json create mode 100644 e2e/playwright/playwright.config.ts create mode 100644 e2e/playwright/project.json create mode 100644 e2e/playwright/runner.mjs create mode 100644 e2e/playwright/serve.mjs create mode 100644 e2e/playwright/tests/container.html create mode 100644 e2e/playwright/tests/editors/checkBox/common.spec.ts create mode 100644 e2e/playwright/tests/navigation/button/common.spec.ts create mode 100644 e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=contained (fluent.blue.light).png create mode 100644 e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=outlined (fluent.blue.light).png create mode 100644 e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=text (fluent.blue.light).png create mode 100644 e2e/playwright/tsconfig.json diff --git a/.github/workflows/testcafe_tests_playwright.yml b/.github/workflows/testcafe_tests_playwright.yml new file mode 100644 index 000000000000..67882266e690 --- /dev/null +++ b/.github/workflows/testcafe_tests_playwright.yml @@ -0,0 +1,321 @@ +name: Playwright tests + +concurrency: + group: wf-${{github.event.pull_request.number || github.sha}}-${{github.workflow}} + cancel-in-progress: true + +on: + pull_request: + paths-ignore: + - 'apps/**/*.md' + merge_group: + workflow_dispatch: + +env: + NX_SKIP_NX_CACHE: ${{ contains(github.event.pull_request.labels.*.name, 'skip-cache') && 'true' || 'false' }} + RUN_TESTS: true + +jobs: + check-should-run: + name: Check if tests should run + runs-on: ubuntu-latest + outputs: + should-run: ${{ steps.check.outputs.should-run }} + styles-changed: ${{ steps.changes.outputs.styles }} + steps: + - uses: actions/checkout@v6 + - name: Check RUN_TESTS flag + id: check + run: echo "should-run=${{ env.RUN_TESTS }}" >> $GITHUB_OUTPUT + + - uses: dorny/paths-filter@v4 + id: changes + with: + filters: | + styles: + - 'packages/devextreme-scss/scss/**' + - 'packages/devextreme-scss/icons/**' + - 'packages/devextreme-scss/images/**' + - 'packages/devextreme-scss/fonts/**' + - 'packages/devextreme/js/**/themes/**' + - 'e2e/playwright/helpers/accessibility/**' + + build: + name: Build DevExtreme + needs: check-should-run + if: needs.check-should-run.outputs.should-run == 'true' + + runs-on: devextreme-shr2 + timeout-minutes: 15 + + steps: + - name: Get sources + uses: actions/checkout@v6 + + - name: Use Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.node-version' + + - uses: pnpm/action-setup@v6 + with: + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v5 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-cache + + - uses: actions/cache@v5 + name: Setup nx cache + with: + path: | + .nx/cache + .nx/workspace-data/*.db + .nx/workspace-data/*.db-wal + .nx/workspace-data/*.db-shm + key: ${{ runner.os }}-nx-v2-${{ github.workflow }}-${{ github.job }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-nx-v2-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}- + ${{ runner.os }}-nx-v2-${{ github.workflow }}-${{ github.job }}- + ${{ runner.os }}-nx-v2- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + shell: bash + env: + NODE_OPTIONS: --max-old-space-size=8192 + run: | + pnpm exec nx build devextreme-scss + pnpm exec nx build devextreme -c testing + + - name: Zip artifacts + working-directory: ./packages/devextreme + run: | + 7z a -tzip -mx3 -mmt2 artifacts.zip artifacts ../devextreme-scss/scss/bundles + + - name: Upload build artifacts + uses: actions/upload-artifact@v7 + with: + name: devextreme-artifacts-playwright + path: ./packages/devextreme/artifacts.zip + retention-days: 1 + + determine-playwright-matrix: + runs-on: ubuntu-latest + name: Determine Playwright matrix + needs: check-should-run + if: needs.check-should-run.outputs.should-run == 'true' + outputs: + matrix: ${{ steps.matrix.outputs.matrix }} + + steps: + - name: Build matrix + id: matrix + run: | + base_matrix_start=$(cat <<'JSON' + [ + { "componentFolder": "accessibility", "name": "accessibility", "concurrency": 6 } + ] + JSON + ) + + base_matrix_end=$(cat <<'JSON' + [ + { "componentFolder": "common", "name": "common", "concurrency": 3 }, + + { "name": "generic", "theme": "generic.light", "concurrency": 2 }, + { "name": "material", "theme": "material.blue.light", "concurrency": 2 }, + { "name": "material - compact", "theme": "material.blue.light.compact", "concurrency": 2 }, + + { "componentFolder": "cardView", "name": "cardView" }, + { "componentFolder": "dataGrid", "name": "dataGrid (1/4)", "indices": "1/4", "concurrency": 5 }, + { "componentFolder": "dataGrid", "name": "dataGrid (2/4)", "indices": "2/4", "concurrency": 5 }, + { "componentFolder": "dataGrid", "name": "dataGrid (3/4)", "indices": "3/4", "concurrency": 5 }, + { "componentFolder": "dataGrid", "name": "dataGrid (4/4)", "indices": "4/4", "concurrency": 5 }, + + { "componentFolder": "editors", "name": "editors" }, + { "componentFolder": "navigation", "name": "navigation", "concurrency": 6 }, + + { "componentFolder": "scheduler/common", "name": "scheduler / common (1/3)", "indices": "1/3", "concurrency": 6 }, + { "componentFolder": "scheduler/common", "name": "scheduler / common (2/3)", "indices": "2/3", "concurrency": 6 }, + { "componentFolder": "scheduler/common", "name": "scheduler / common (3/3)", "indices": "3/3", "concurrency": 6 }, + { "componentFolder": "scheduler/viewOffset", "name": "scheduler / viewOffset (1/2)", "indices": "1/2", "concurrency": 4 }, + { "componentFolder": "scheduler/viewOffset", "name": "scheduler / viewOffset (2/2)", "indices": "2/2", "concurrency": 4 }, + { "componentFolder": "scheduler/timezones", "name": "scheduler / timezones (Europe/Berlin)", "timezone": "Europe/Berlin" }, + { "componentFolder": "scheduler/timezones", "name": "scheduler / timezones (America/Los_Angeles)", "timezone": "America/Los_Angeles" } + ] + JSON + ) + + style_dependent_matrix=$(cat <<'JSON' + [ + { "componentFolder": "accessibility", "name": "accessibility - fluent.dark", "theme": "fluent.blue.dark", "concurrency": 6 }, + { "componentFolder": "accessibility", "name": "accessibility - material.light", "theme": "material.blue.light", "concurrency": 6 }, + { "componentFolder": "accessibility", "name": "accessibility - material.dark", "theme": "material.blue.dark", "concurrency": 6 } + ] + JSON + ) + + matrix=$(jq -c -n \ + --argjson baseStart "$base_matrix_start" \ + --argjson baseEnd "$base_matrix_end" \ + --argjson styleDependent "$style_dependent_matrix" \ + --arg stylesChanged "${{ needs.check-should-run.outputs.styles-changed }}" \ + '{ include: ($baseStart + (if $stylesChanged == "true" then $styleDependent else [] end) + $baseEnd) }') + + echo "matrix=$matrix" >> $GITHUB_OUTPUT + + playwright: + name: ${{ matrix.name }}${{ !matrix.theme && ' - fluent' || '' }} + if: | + needs.check-should-run.outputs.should-run == 'true' && + needs.determine-playwright-matrix.result == 'success' && + needs.build.result == 'success' + needs: [check-should-run, build, determine-playwright-matrix] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.determine-playwright-matrix.outputs.matrix) }} + runs-on: devextreme-shr2 + timeout-minutes: 45 + + steps: + - name: Get sources + uses: actions/checkout@v6 + + - name: Download artifacts + uses: actions/download-artifact@v8 + with: + name: devextreme-artifacts-playwright + path: ./packages/devextreme + + - name: Unpack artifacts + working-directory: ./packages/devextreme + run: 7z x artifacts.zip -aoa + + - name: Setup Chrome + uses: ./.github/actions/setup-chrome + with: + chrome-version: '150.0.7871.181' + + - name: Use Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.node-version' + + - uses: pnpm/action-setup@v6 + with: + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache/restore@v5 + name: Restore pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-cache + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run Playwright tests + working-directory: ./e2e/playwright + env: + NODE_OPTIONS: --max-old-space-size=8192 + # The browser context takes the timezone, so the agent clock is left alone. + TIMEZONE: ${{ matrix.timezone }} + run: | + THEME="--theme ${{ matrix.theme || 'fluent.blue.light' }}" + [ "${{ matrix.componentFolder }}" != "" ] && COMPONENT_FOLDER="--componentFolder ${{ matrix.componentFolder }}" + [ "${{ matrix.indices }}" != "" ] && INDICES="--indices ${{ matrix.indices }}" + [ "${{ matrix.concurrency }}" != "" ] && CONCURRENCY="--concurrency ${{ matrix.concurrency }}" + all_args="$COMPONENT_FOLDER $CONCURRENCY $INDICES $THEME" + echo "$all_args" + pnpm run test $all_args + + - name: Sanitize job name + if: ${{ always() }} + run: echo "JOB_NAME=$(echo "${{ matrix.name }}" | tr '/' '-')" >> $GITHUB_ENV + + - name: Upload test report + if: ${{ failure() }} + uses: actions/upload-artifact@v7 + with: + name: playwright-report-${{ env.JOB_NAME }} + path: ${{ github.workspace }}/e2e/playwright/artifacts/playwright-report + if-no-files-found: ignore + + - name: Upload test traces and screenshot diffs + if: ${{ failure() }} + uses: actions/upload-artifact@v7 + with: + name: playwright-traces-${{ env.JOB_NAME }} + path: ${{ github.workspace }}/e2e/playwright/artifacts/test-results + if-no-files-found: ignore + + merge-artifacts: + runs-on: ubuntu-latest + needs: [playwright] + if: ${{ always() }} + + steps: + - name: Detect mergeable artifacts + id: mergeable-artifacts + uses: actions/github-script@v9 + with: + script: | + const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, { + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId, + per_page: 100, + }); + + const artifactGroups = { + reports: /^playwright-report-/, + traces: /^playwright-traces-/, + }; + + for (const [outputName, pattern] of Object.entries(artifactGroups)) { + const names = artifacts + .map((artifact) => artifact.name) + .filter((name) => pattern.test(name)); + + core.setOutput(outputName, names.length > 0 ? 'true' : 'false'); + core.info(`${outputName}: ${names.length ? names.join(', ') : '(none)'}`); + } + + - name: Merge test reports + if: steps.mergeable-artifacts.outputs.reports == 'true' + uses: actions/upload-artifact/merge@v7 + continue-on-error: true + with: + name: playwright-reports + pattern: playwright-report-* + separate-directories: true + delete-merged: true + + - name: Merge test traces + if: steps.mergeable-artifacts.outputs.traces == 'true' + uses: actions/upload-artifact/merge@v7 + continue-on-error: true + with: + name: playwright-traces + pattern: playwright-traces-* + separate-directories: true + delete-merged: true diff --git a/e2e/playwright/.gitignore b/e2e/playwright/.gitignore new file mode 100644 index 000000000000..d4f588edfef5 --- /dev/null +++ b/e2e/playwright/.gitignore @@ -0,0 +1 @@ +artifacts/ diff --git a/e2e/playwright/README.md b/e2e/playwright/README.md new file mode 100644 index 000000000000..35b78a12809b --- /dev/null +++ b/e2e/playwright/README.md @@ -0,0 +1,100 @@ +# Component E2E tests (Playwright) + +End-to-end tests for DevExtreme components. They run on [Playwright](https://playwright.dev/) and +use the system Google Chrome (`channel: 'chrome'`), so no browser download is needed. + +While the migration is in progress, `e2e/testcafe-devextreme` stays the source of truth: this +package holds the tests that are already migrated, and its workflow runs the same matrix so both +runs stay comparable. + +## Run locally + +```bash +pnpm install --frozen-lockfile +pnpm exec nx build devextreme-scss +pnpm exec nx build devextreme -c testing + +cd e2e/playwright +pnpm run test # everything, fluent.blue.light +pnpm run test --componentFolder navigation # one component folder +pnpm run test --theme material.blue.light # another theme +pnpm run test --indices 1/4 --concurrency 5 # one shard, five workers +pnpm run test --test "Buttons, stylingMode=text" # one test by name +``` + +The runner takes the flags the TestCafe one takes (`--componentFolder`, `--indices`, `--theme`, +`--concurrency`, `--test`) and passes everything else straight to `playwright test`, so +`pnpm run test --headed --debug` works too. The static server for the test page starts on its own. + +## Screenshots + +Etalons live in `etalons/` next to the test and carry the theme in the file name — +`Buttons, stylingMode=text (fluent.blue.light).png` — the same convention the TestCafe run uses. + +The pixel budget is asymmetric on purpose: **CI is the source of truth** (`maxDiffPixelRatio` +0.001, `threshold` 0.1) and a local run is a sanity check (0.05 / 0.2). + +Run the screenshot tests in the container and nowhere else. The etalons are Linux renders, and a +macOS or Windows one differs in text metrics — the image comes out a different size, which no +tolerance can absorb. The same goes for writing them: + +```bash +docker/run.sh # verify +docker/run.sh --update-snapshots # rewrite the etalons of the tests that ran +``` + +## Run in the CI environment + +The container takes Node from `.node-version` and Chrome from the version the workflow installs; +dependencies and the DevExtreme build come from the host. + +```bash +docker/run.sh # the whole suite, as CI runs it +docker/run.sh --ui # then open http://localhost:9323 +``` + +Open the UI on `localhost` — the address Playwright prints, `0.0.0.0`, is not a secure origin and +its trace viewer will not load there. + +## Read a failed CI run + +```bash +gh run download -n playwright-report-navigation -D ci-report +gh run download -n playwright-traces-navigation -D ci-traces +pnpm exec playwright show-report ci-report +pnpm exec playwright show-trace ci-traces//trace.zip --port 0 +``` + +The trace holds every action with the DOM before and after it, the network and the console, and a +failed screenshot comparison uploads its `-expected`, `-actual` and `-diff` images alongside, so a +failure that only happens on CI can be diagnosed without reproducing it. + +## Writing a test + +```ts +import { expect, test } from '../../../fixtures'; +import { createWidget } from '../../../helpers/createWidget'; +import { testScreenshot } from '../../../helpers/screenshots'; +import Button from '../../../models/button'; + +test('Button reports its text', async ({ page }) => { + await createWidget(page, 'dxButton', { text: 'Find' }); + + const button = new Button(page, '#container .dx-button'); + + await expect(button.text).toHaveText('Find'); + await testScreenshot(page, 'Button with text.png'); +}); +``` + +`page` arrives ready: the container page is open, the theme requested by `--theme` is applied, the +viewport is 1200×800 and the widgets of the previous test are disposed. + +| TestCafe | here | +|---------------------------------|-----------------------------------------------| +| `Selector` | `Locator` (`models/`) | +| `ClientFunction` | `page.evaluate` / `locator.evaluate` | +| `devextreme-screenshot-comparer`| `testScreenshot` on top of `toHaveScreenshot` | +| `RequestMock` | `mockApi` on top of `page.route` | +| `mockdate` | `mockDate` on top of `page.clock` | +| machine timezone on the agent | `TIMEZONE` → the browser context | diff --git a/e2e/playwright/docker/Dockerfile b/e2e/playwright/docker/Dockerfile new file mode 100644 index 000000000000..296435b9cc75 --- /dev/null +++ b/e2e/playwright/docker/Dockerfile @@ -0,0 +1,17 @@ +# Mirrors the environment the tests get on CI. +FROM ubuntu:24.04 + +ARG NODE_VERSION +ARG CHROME_VERSION + +RUN echo 'APT::Install-Recommends "0";' > /etc/apt/apt.conf.d/dx-no-recommends \ + && apt-get update \ + && apt-get install -y ca-certificates curl fonts-noto-color-emoji \ + && curl -fsSL "https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}-1_amd64.deb" -o /tmp/chrome.deb \ + && apt-get install -y /tmp/chrome.deb \ + && curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" -o /tmp/node.tar.gz \ + && tar -xzf /tmp/node.tar.gz -C /usr/local --strip-components=1 \ + && rm /tmp/chrome.deb /tmp/node.tar.gz \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /repo/e2e/playwright diff --git a/e2e/playwright/docker/run.sh b/e2e/playwright/docker/run.sh new file mode 100755 index 000000000000..006ed53f6020 --- /dev/null +++ b/e2e/playwright/docker/run.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Usage: docker/run.sh [--ui] [playwright args] +set -euo pipefail + +UI_PORT=9323 +UI=false + +if [ "${1:-}" = "--ui" ]; then + UI=true + shift +fi + +REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +NODE_VERSION="$(cat "$REPO_ROOT/.node-version")" +CHROME_VERSION="$(grep -m1 "chrome-version:" "$REPO_ROOT/.github/workflows/testcafe_tests_playwright.yml" | cut -d: -f2 | tr -d " '\"")" + +echo "Node $NODE_VERSION, Google Chrome $CHROME_VERSION" + +docker build --platform linux/amd64 \ + --build-arg "NODE_VERSION=$NODE_VERSION" \ + --build-arg "CHROME_VERSION=$CHROME_VERSION" \ + -t devextreme-playwright-e2e "$REPO_ROOT/e2e/playwright/docker" + +RUN=(docker run --rm --platform linux/amd64 --shm-size=2gb --security-opt seccomp=unconfined + -v "$REPO_ROOT:/repo" -w /repo/e2e/playwright) + +if [ "$UI" = true ]; then + echo "UI mode: open http://localhost:$UI_PORT" + exec "${RUN[@]}" -p "$UI_PORT:$UI_PORT" devextreme-playwright-e2e \ + node_modules/.bin/playwright test --ui-host=0.0.0.0 --ui-port="$UI_PORT" "$@" +fi + +exec "${RUN[@]}" -e CI=true devextreme-playwright-e2e node_modules/.bin/playwright test "$@" diff --git a/e2e/playwright/eslint.config.mjs b/e2e/playwright/eslint.config.mjs new file mode 100644 index 000000000000..704158b41664 --- /dev/null +++ b/e2e/playwright/eslint.config.mjs @@ -0,0 +1,79 @@ +/* eslint-disable spellcheck/spell-checker */ +import noOnlyTests from 'eslint-plugin-no-only-tests'; +import tsParser from '@typescript-eslint/parser'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import stylistic from '@stylistic/eslint-plugin'; +import { changeRulesToStylistic } from 'eslint-migration-utils'; +import spellCheckConfig from 'eslint-config-devextreme/spell-check'; +import typescriptConfig from 'eslint-config-devextreme/typescript'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default [ + { + ignores: [ + 'artifacts/**', + 'node_modules/**', + ], + }, + ...spellCheckConfig, + { + plugins: { + 'no-only-tests': noOnlyTests, + }, + settings: { + 'import/resolver': { + node: { + extensions: ['.js', '.mjs', '.ts'], + }, + }, + }, + }, + ...typescriptConfig.map((config) => { + const newConfig = { + ...config, + files: ['**/*.ts'], + }; + + if (config.rules) { + newConfig.rules = changeRulesToStylistic(config.rules); + } + + return newConfig; + }), + { + files: ['**/*.ts'], + plugins: { + '@stylistic': stylistic, + }, + languageOptions: { + parser: tsParser, + sourceType: 'module', + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname, + }, + }, + rules: { + '@typescript-eslint/no-unused-vars': ['error', { + ignoreRestSiblings: true, + caughtErrors: 'none', + }], + 'no-only-tests/no-only-tests': 'error', + 'no-await-in-loop': 'off', + 'no-restricted-globals': 'off', + 'no-restricted-syntax': 'off', + 'require-await': 'off', + '@typescript-eslint/require-await': 'off', + 'import/no-extraneous-dependencies': 'off', + 'import/prefer-default-export': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@stylistic/no-extra-parens': 'off', + }, + }, +]; diff --git a/e2e/playwright/fixtures.ts b/e2e/playwright/fixtures.ts new file mode 100644 index 000000000000..42e03d50e0db --- /dev/null +++ b/e2e/playwright/fixtures.ts @@ -0,0 +1,35 @@ +import { test as base } from '@playwright/test'; +import { DEFAULT_BROWSER_SIZE, DEFAULT_THEME } from './helpers/const'; +import { + changeTheme, + clearTestPage, + openTestPage, + resetPageState, +} from './helpers/testPageUtils'; + +export interface TestOptions { + theme: string; + browserSize: [number, number]; +} + +// Overriding "page" gives every test the prepared container page, the way the TestCafe runner +// did it from its global test hooks. +export const test = base.extend({ + theme: [DEFAULT_THEME, { option: true }], + browserSize: [DEFAULT_BROWSER_SIZE, { option: true }], + + page: async ({ page, theme, browserSize }, use) => { + const [width, height] = browserSize; + + await page.setViewportSize({ width, height }); + await openTestPage(page); + await changeTheme(page, theme); + await resetPageState(page); + + await use(page); + + await clearTestPage(page); + }, +}); + +export { expect } from '@playwright/test'; diff --git a/e2e/playwright/helpers/apiMock.ts b/e2e/playwright/helpers/apiMock.ts new file mode 100644 index 000000000000..85cef2471226 --- /dev/null +++ b/e2e/playwright/helpers/apiMock.ts @@ -0,0 +1,23 @@ +import type { Page } from '@playwright/test'; + +export interface MockedRequest { + url: RegExp | string; + body: unknown; + status?: number; + headers?: Record; +} + +// Replaces the TestCafe RequestMock: the rules are matched in the order they are declared, +// so a narrower rule has to come before the wider one, just as it did there. +export async function mockApi(page: Page, requests: MockedRequest[]): Promise { + await Promise.all(requests.map(({ + url, body, status = 200, headers, + }) => page.route(url, async (route) => { + await route.fulfill({ + status, + contentType: 'application/json', + headers: { 'access-control-allow-origin': '*', ...headers }, + body: JSON.stringify(body), + }); + }))); +} diff --git a/e2e/playwright/helpers/const.ts b/e2e/playwright/helpers/const.ts new file mode 100644 index 000000000000..7dafa3374eca --- /dev/null +++ b/e2e/playwright/helpers/const.ts @@ -0,0 +1,9 @@ +export const DEFAULT_BROWSER_SIZE: [number, number] = [1200, 800]; + +export const DEFAULT_THEME = 'fluent.blue.light'; + +export const DEFAULT_SELECTOR = '#container'; + +export const SERVER_PORT = 8080; + +export const TEST_PAGE_URL = '/e2e/playwright/tests/container.html'; diff --git a/e2e/playwright/helpers/createWidget.ts b/e2e/playwright/helpers/createWidget.ts new file mode 100644 index 000000000000..4559a84ff72a --- /dev/null +++ b/e2e/playwright/helpers/createWidget.ts @@ -0,0 +1,32 @@ +import type { Page } from '@playwright/test'; +import type { WidgetName, WidgetOptions } from '../models/types'; +import { DEFAULT_SELECTOR } from './const'; + +export interface CreateWidgetOptions { + disableFxAnimation: boolean; +} + +const DEFAULT_OPTIONS: CreateWidgetOptions = { + disableFxAnimation: true, +}; + +export const createWidget = async ( + page: Page, + widgetName: TWidgetName, + widgetOptions: TWidgetName extends keyof WidgetOptions ? WidgetOptions[TWidgetName] : unknown, + selector: string = DEFAULT_SELECTOR, + { disableFxAnimation } = DEFAULT_OPTIONS, +): Promise => page.evaluate( + ({ + name, options, elementSelector, disableAnimation, + }) => { + (window as any).DevExpress.fx.off = disableAnimation; + (window as any).widget = ($(elementSelector) as any)[name](options)[name]('instance'); + }, + { + name: widgetName, + options: widgetOptions, + elementSelector: selector, + disableAnimation: disableFxAnimation, + }, +); diff --git a/e2e/playwright/helpers/domUtils.ts b/e2e/playwright/helpers/domUtils.ts new file mode 100644 index 000000000000..e9c35622cadd --- /dev/null +++ b/e2e/playwright/helpers/domUtils.ts @@ -0,0 +1,126 @@ +import type { Page } from '@playwright/test'; + +const STYLESHEET_RULES_ID = 'stylesheetRules'; + +export const setAttribute = async ( + page: Page, + selector: string, + attribute: string, + value: string, +): Promise => page.evaluate( + ({ elementSelector, name, attributeValue }) => { + document.querySelector(elementSelector)?.setAttribute(name, attributeValue); + }, + { elementSelector: selector, name: attribute, attributeValue: value }, +); + +export const removeAttribute = async ( + page: Page, + selector: string, + attribute: string, +): Promise => page.evaluate( + ({ elementSelector, name }) => { + document.querySelector(elementSelector)?.removeAttribute(name); + }, + { elementSelector: selector, name: attribute }, +); + +export const appendAttributeValue = async ( + page: Page, + selector: string, + attribute: string, + value: string, +): Promise => page.evaluate( + ({ elementSelector, name, attributeValue }) => { + const element = document.querySelector(elementSelector); + const current = element?.getAttribute(name) ?? ''; + + element?.setAttribute(name, `${current} ${attributeValue}`); + }, + { elementSelector: selector, name: attribute, attributeValue: value }, +); + +export const setStyleAttribute = async ( + page: Page, + selector: string, + value: string, +): Promise => appendAttributeValue(page, selector, 'style', value); + +export const setClassAttribute = async ( + page: Page, + selector: string, + value: string, +): Promise => appendAttributeValue(page, selector, 'class', value); + +export const removeClassAttribute = async ( + page: Page, + selector: string, + value: string, +): Promise => page.evaluate( + ({ elementSelector, className }) => { + const element = document.querySelector(elementSelector); + const classes = element?.getAttribute('class') ?? ''; + + element?.setAttribute('class', classes.replace(className, '')); + }, + { elementSelector: selector, className: value }, +); + +export const appendElementTo = async ( + page: Page, + parentSelector: string, + tagName: string, + id: string, + style: Record = {}, +): Promise => page.evaluate( + ({ + parent, tag, elementId, elementStyle, + }) => { + const element = document.createElement(tag); + + element.setAttribute('id', elementId); + Object.entries(elementStyle).forEach(([key, value]) => { element.style[key] = value; }); + + document.querySelector(parent)?.appendChild(element); + }, + { + parent: parentSelector, tag: tagName, elementId: id, elementStyle: style, + }, +); + +export const addCaptionTo = async ( + page: Page, + parentSelector: string, + caption: string, +): Promise => page.evaluate( + ({ parent, text }) => { + const element = document.createElement('div'); + + element.textContent = text; + document.querySelector(parent)?.appendChild(element); + }, + { parent: parentSelector, text: caption }, +); + +export const insertStylesheetRulesToPage = async ( + page: Page, + rules: string, +): Promise => page.evaluate( + ({ stylesheetId, cssRules }) => { + let style = document.getElementById(stylesheetId) as HTMLStyleElement | null; + + if (!style) { + style = document.createElement('style'); + style.id = stylesheetId; + document.head.appendChild(style); + } + + style.sheet?.insertRule(cssRules, style.sheet.cssRules.length); + }, + { stylesheetId: STYLESHEET_RULES_ID, cssRules: rules }, +); + +export const removeStylesheetRulesFromPage = async (page: Page): Promise => page.evaluate( + (stylesheetId) => { document.getElementById(stylesheetId)?.remove(); }, + STYLESHEET_RULES_ID, +); diff --git a/e2e/playwright/helpers/mockDate.ts b/e2e/playwright/helpers/mockDate.ts new file mode 100644 index 000000000000..d32cdb8a6b5e --- /dev/null +++ b/e2e/playwright/helpers/mockDate.ts @@ -0,0 +1,10 @@ +import type { Page } from '@playwright/test'; + +// Runs before any page script, so the widgets see the mocked date from their very first render. +export async function mockDate(page: Page, date: string): Promise { + await page.clock.install({ time: new Date(date) }); +} + +export async function setSystemTime(page: Page, date: string): Promise { + await page.clock.setSystemTime(new Date(date)); +} diff --git a/e2e/playwright/helpers/screenshots.ts b/e2e/playwright/helpers/screenshots.ts new file mode 100644 index 000000000000..01971f99f69d --- /dev/null +++ b/e2e/playwright/helpers/screenshots.ts @@ -0,0 +1,74 @@ +import type { Locator, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import { DEFAULT_SELECTOR } from './const'; +import { getFullThemeName, getThemePostfix } from './themeUtils'; +import { changeTheme } from './testPageUtils'; + +export interface ScreenshotOptions { + element?: Locator | string | null; + theme?: string; + shouldTestInCompact?: boolean; + compactCallBack?: () => Promise; + themeChanged?: () => Promise; +} + +// The etalons carry the theme in their file name — the same convention the TestCafe run uses, +// so the existing " (fluent.blue.light).png" files are reused as is. +const getScreenshotName = (baseName: string, theme?: string): string => { + const themePostfix = getThemePostfix(theme); + + return baseName.endsWith('.png') + ? baseName.replace('.png', `${themePostfix}.png`) + : `${baseName}${themePostfix}.png`; +}; + +const resolveTarget = ( + page: Page, + element: Locator | string | null | undefined, +): Locator | Page => { + if (element === null) { + return page; + } + + if (typeof element === 'string') { + return page.locator(element); + } + + return element ?? page.locator(DEFAULT_SELECTOR); +}; + +export async function testScreenshot( + page: Page, + screenshotName: string, + options?: ScreenshotOptions, +): Promise { + const { + element, + theme, + shouldTestInCompact = false, + compactCallBack, + themeChanged, + } = options ?? {}; + + if (theme) { + await changeTheme(page, theme); + await themeChanged?.(); + } + + const target = resolveTarget(page, element); + + await expect(target).toHaveScreenshot([getScreenshotName(screenshotName, theme)]); + + if (shouldTestInCompact) { + const themeName = theme ?? getFullThemeName(); + + await changeTheme(page, `${themeName}.compact`); + await compactCallBack?.(); + + await expect(target).toHaveScreenshot([getScreenshotName(screenshotName, `${themeName}.compact`)]); + } + + if (theme || shouldTestInCompact) { + await changeTheme(page, getFullThemeName()); + } +} diff --git a/e2e/playwright/helpers/testPageUtils.ts b/e2e/playwright/helpers/testPageUtils.ts new file mode 100644 index 000000000000..3fe744e79eaf --- /dev/null +++ b/e2e/playwright/helpers/testPageUtils.ts @@ -0,0 +1,82 @@ +import type { Page } from '@playwright/test'; +import { DEFAULT_THEME, TEST_PAGE_URL } from './const'; + +export async function openTestPage(page: Page): Promise { + await page.goto(TEST_PAGE_URL); + await page.waitForFunction(() => !!(window as any).DevExpress); +} + +export async function changeTheme(page: Page, themeName: string): Promise { + await page.evaluate(async (theme) => { + const { themes } = (window as any).DevExpress.ui; + + if (themes.current() === theme) { + return; + } + + await new Promise((resolve) => { + themes.ready(resolve); + themes.current(theme); + }); + }, themeName); +} + +export async function getCurrentTheme(page: Page): Promise { + const theme = await page.evaluate(() => (window as any).DevExpress?.ui.themes.current()); + + return theme ?? DEFAULT_THEME; +} + +export async function clearTestPage(page: Page): Promise { + await page.evaluate(() => { + const widgetSelector = '.dx-widget'; + const $elements = $(widgetSelector) + .filter((_, element) => $(element).parents(widgetSelector).length === 0); + + $elements.each((_, element) => { + const $widgetElement = $(element); + const widgetNames = $widgetElement.data().dxComponents; + + widgetNames?.forEach((name) => { + if ($widgetElement.hasClass('dx-widget')) { + ($widgetElement as any)[name]?.('dispose'); + } + }); + $widgetElement.empty(); + }); + + document.getElementById('focusable-start')?.remove(); + document.getElementById('stylesheetRules')?.remove(); + + const body = document.querySelector('body'); + + if (body) { + body.innerHTML = ''; + body.className = 'dx-surface'; + } + + const temp = document.createElement('div'); + + temp.innerHTML = ` +
+

Test header

+
+
+
+ `; + + body?.prepend(temp.firstElementChild!); + }); +} + +export async function resetPageState(page: Page): Promise { + await page.evaluate(() => { + if (document.activeElement && document.activeElement !== document.body) { + (document.activeElement as HTMLElement).blur(); + } + + window.getSelection()?.removeAllRanges(); + }); + + await page.mouse.move(1, 1); +} diff --git a/e2e/playwright/helpers/themeUtils.ts b/e2e/playwright/helpers/themeUtils.ts new file mode 100644 index 000000000000..143c2d98f2ef --- /dev/null +++ b/e2e/playwright/helpers/themeUtils.ts @@ -0,0 +1,15 @@ +import { DEFAULT_THEME } from './const'; + +export const getFullThemeName = (): string => process.env.THEME ?? DEFAULT_THEME; + +export const getThemeName = (): string => getFullThemeName().split('.')[0]; + +export const getDarkThemeName = (): string => getFullThemeName().replace('light', 'dark'); + +export const isMaterial = (): boolean => getFullThemeName().startsWith('material'); + +export const isFluent = (): boolean => getFullThemeName().startsWith('fluent'); + +export const isMaterialBased = (): boolean => isMaterial() || isFluent(); + +export const getThemePostfix = (theme?: string): string => ` (${theme ?? getFullThemeName()})`; diff --git a/e2e/playwright/models/button.ts b/e2e/playwright/models/button.ts new file mode 100644 index 000000000000..81df6f59b1d2 --- /dev/null +++ b/e2e/playwright/models/button.ts @@ -0,0 +1,24 @@ +import type { Locator } from '@playwright/test'; +import type { WidgetName } from './types'; +import Widget from './internal/widget'; + +export const CLASS = { + text: '.dx-button-text', + selected: 'dx-state-selected', + itemSelected: 'dx-item-selected', +}; + +export default class Button extends Widget { + public static className = 'dx-button'; + + public get text(): Locator { + return this.element.locator(CLASS.text); + } + + public isSelected(): Promise { + return this.hasClass(CLASS.selected); + } + + // eslint-disable-next-line class-methods-use-this + public getName(): WidgetName { return 'dxButton'; } +} diff --git a/e2e/playwright/models/checkBox.ts b/e2e/playwright/models/checkBox.ts new file mode 100644 index 000000000000..0ba7c189b705 --- /dev/null +++ b/e2e/playwright/models/checkBox.ts @@ -0,0 +1,23 @@ +import type { WidgetName } from './types'; +import Widget from './internal/widget'; + +export const CLASS = { + checked: 'dx-checkbox-checked', + indeterminate: 'dx-checkbox-indeterminate', + icon: '.dx-checkbox-icon', +}; + +export default class CheckBox extends Widget { + public static className = 'dx-checkbox'; + + public isChecked(): Promise { + return this.hasClass(CLASS.checked); + } + + public isIndeterminate(): Promise { + return this.hasClass(CLASS.indeterminate); + } + + // eslint-disable-next-line class-methods-use-this + public getName(): WidgetName { return 'dxCheckBox'; } +} diff --git a/e2e/playwright/models/internal/widget.ts b/e2e/playwright/models/internal/widget.ts new file mode 100644 index 000000000000..5248e39ae630 --- /dev/null +++ b/e2e/playwright/models/internal/widget.ts @@ -0,0 +1,85 @@ +import type { Locator, Page } from '@playwright/test'; +import type { WidgetName } from '../types'; + +const CLASS = { + focused: 'dx-state-focused', + hovered: 'dx-state-hover', + active: 'dx-state-active', + disabled: 'dx-state-disabled', +}; + +export default abstract class Widget { + public static className = ''; + + public readonly page: Page; + + public readonly element: Locator; + + constructor(page: Page, selector: Locator | string) { + this.page = page; + this.element = typeof selector === 'string' ? page.locator(selector) : selector; + } + + static addClassPrefix(widgetName: string, className: string): string { + return `dx-${widgetName.slice(2).toLowerCase() + (className ? `-${className}` : '')}`; + } + + public isFocused(): Promise { + return this.hasClass(CLASS.focused); + } + + public isHovered(): Promise { + return this.hasClass(CLASS.hovered); + } + + public isActive(): Promise { + return this.hasClass(CLASS.active); + } + + public isDisabled(): Promise { + return this.hasClass(CLASS.disabled); + } + + public async hasClass(className: string): Promise { + const classes = await this.element.getAttribute('class'); + + return classes?.split(' ').includes(className) ?? false; + } + + public async option(option: string | Record, ...value: unknown[]): Promise { + const isSetter = value.length > 0 || typeof option === 'object'; + + return isSetter + ? this.invoke('option', option, ...value) + : this.invoke('option', option); + } + + public async focus(): Promise { + await this.invoke('focus'); + } + + public async repaint(): Promise { + await this.invoke('repaint'); + } + + public async invoke(method: string, ...args: unknown[]): Promise { + return this.element.evaluate( + (element, { name, methodArgs, widgetName }) => { + const $element = $(element); + const data = $element.data(); + const widgetNames = data.dxComponents ?? []; + + if (!widgetNames.length) { + throw new Error(`jQuery widget not found for element ${element.outerHTML.slice(0, 100)}`); + } + + const instance = data[widgetName] ?? data[widgetNames[0]]; + + return instance[name](...methodArgs); + }, + { name: method, methodArgs: args, widgetName: this.getName() }, + ); + } + + abstract getName(): WidgetName; +} diff --git a/e2e/playwright/models/types.ts b/e2e/playwright/models/types.ts new file mode 100644 index 000000000000..95664ba3611f --- /dev/null +++ b/e2e/playwright/models/types.ts @@ -0,0 +1,80 @@ +import type { Properties as DataGridProperties } from 'devextreme/ui/data_grid'; +import type { Properties as FilterBuilderProperties } from 'devextreme/ui/filter_builder'; +import type { Properties as GanttProperties } from 'devextreme/ui/gantt'; + +export type WidgetName = 'dxAccordion' + | 'dxActionSheet' + | 'dxAutocomplete' + | 'dxButton' + | 'dxButtonGroup' + | 'dxCalendar' + | 'dxCalendarView' + | 'dxCardView' + | 'dxChart' + | 'dxChat' + | 'dxCheckBox' + | 'dxColorBox' + | 'dxContextMenu' + | 'dxDataGrid' + | 'dxDateBox' + | 'dxDateRangeBox' + | 'dxDrawer' + | 'dxDropDownBox' + | 'dxDropDownButton' + | 'dxDropDownMenu' + | 'dxDraggable' + | 'dxFileManager' + | 'dxFileUploader' + | 'dxFilterBuilder' + | 'dxForm' + | 'dxGallery' + | 'dxHtmlEditor' + | 'dxList' + | 'dxLoadIndicator' + | 'dxLoadPanel' + | 'dxLookup' + | 'dxMap' + | 'dxMenu' + | 'dxMultiView' + | 'dxNumberBox' + | 'dxOverlay' + | 'dxPagination' + | 'dxPivotGrid' + | 'dxPivotGridFieldChooser' + | 'dxPopover' + | 'dxPopup' + | 'dxProgressBar' + | 'dxRadioGroup' + | 'dxRangeSlider' + | 'dxScheduler' + | 'dxScrollable' + | 'dxScrollView' + | 'dxSelectBox' + | 'dxSlider' + | 'dxSortable' + | 'dxSpeechToText' + | 'dxSpeedDialAction' + | 'dxSplitter' + | 'dxStepper' + | 'dxSwitch' + | 'dxTabPanel' + | 'dxTabs' + | 'dxTagBox' + | 'dxTextArea' + | 'dxTextBox' + | 'dxTileView' + | 'dxToast' + | 'dxToolbar' + | 'dxTooltip' + | 'dxTreeList' + | 'dxTreeView' + | 'dxValidationSummary' + | 'dxValidator' + | 'dxGantt'; + +export interface WidgetOptions { + dxDataGrid: DataGridProperties; + dxFilterBuilder: FilterBuilderProperties; + dxGantt: GanttProperties; + // TODO: write other widgets +} diff --git a/e2e/playwright/package.json b/e2e/playwright/package.json new file mode 100644 index 000000000000..810adaae49d9 --- /dev/null +++ b/e2e/playwright/package.json @@ -0,0 +1,24 @@ +{ + "name": "devextreme-playwright-tests", + "version": "26.2.0", + "scripts": { + "test": "node ./runner.mjs", + "lint": "eslint" + }, + "devDependencies": { + "@playwright/test": "catalog:", + "@types/jquery": "catalog:", + "devextreme": "workspace:*", + "eslint": "catalog:", + "@eslint/js": "catalog:", + "@stylistic/eslint-plugin": "catalog:", + "@typescript-eslint/eslint-plugin": "catalog:", + "@typescript-eslint/parser": "catalog:", + "eslint-config-devextreme": "catalog:", + "eslint-plugin-import": "catalog:", + "eslint-plugin-no-only-tests": "catalog:", + "typescript": "5.8.3", + "eslint-migration-utils": "workspace:*", + "@types/node": "catalog:tools" + } +} diff --git a/e2e/playwright/playwright.config.ts b/e2e/playwright/playwright.config.ts new file mode 100644 index 000000000000..00f9c7c3bc1e --- /dev/null +++ b/e2e/playwright/playwright.config.ts @@ -0,0 +1,73 @@ +import { defineConfig } from '@playwright/test'; +import type { TestOptions } from './fixtures'; +import { + DEFAULT_BROWSER_SIZE, DEFAULT_THEME, SERVER_PORT, TEST_PAGE_URL, +} from './helpers/const'; + +const baseURL = `http://localhost:${SERVER_PORT}`; +const [width, height] = DEFAULT_BROWSER_SIZE; + +// The pixel budget is asymmetric on purpose: CI is the source of truth and the etalons are +// generated there, a local run only says whether the change is in the right ballpark. +const screenshotBudget = process.env.CI + ? { maxDiffPixelRatio: 0.001, threshold: 0.1 } + : { maxDiffPixelRatio: 0.05, threshold: 0.2 }; + +export default defineConfig({ + testDir: './tests', + snapshotPathTemplate: '{testDir}/{testFileDir}/etalons/{arg}{ext}', + outputDir: './artifacts/test-results', + fullyParallel: true, + forbidOnly: !!process.env.CI, + workers: process.env.CI ? 8 : 4, + reporter: process.env.CI + ? [['list'], ['html', { open: 'never', outputFolder: './artifacts/playwright-report' }]] + : [['list']], + timeout: 60000, + expect: { + timeout: 5000, + toHaveScreenshot: screenshotBudget, + }, + use: { + theme: process.env.THEME ?? DEFAULT_THEME, + // The TestCafe run changes the machine timezone on the CI agent; the browser context + // takes it per run instead, so the same agent can serve any timezone job. + timezoneId: process.env.TIMEZONE ?? 'GMT', + baseURL, + channel: 'chrome', + headless: true, + viewport: { width, height }, + launchOptions: { + // The same flags the TestCafe runner passes as the "chrome:devextreme-shr2" alias: + // they pin the rendering pipeline so screenshots stay comparable between runs. + args: [ + '--no-sandbox', + '--disable-dev-shm-usage', + '--disable-gpu', + '--disable-partial-raster', + '--disable-skia-runtime-opts', + '--run-all-compositor-stages-before-draw', + '--disable-new-content-rendering-timeout', + '--disable-threaded-animation', + '--disable-threaded-scrolling', + '--disable-checker-imaging', + '--disable-image-animation-resync', + '--use-gl=swiftshader', + '--disable-features=PaintHolding', + '--js-flags=--random-seed=2147483647', + '--font-render-hinting=none', + '--disable-font-subpixel-positioning', + ], + // Playwright hides scrollbars in headless by default, which changes the page layout. + ignoreDefaultArgs: ['--hide-scrollbars'], + }, + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + }, + webServer: { + command: `node ./serve.mjs --port=${SERVER_PORT}`, + url: `${baseURL}${TEST_PAGE_URL}`, + reuseExistingServer: !process.env.CI, + timeout: 30000, + }, +}); diff --git a/e2e/playwright/project.json b/e2e/playwright/project.json new file mode 100644 index 000000000000..8eb0a9e08b1a --- /dev/null +++ b/e2e/playwright/project.json @@ -0,0 +1,34 @@ +{ + "name": "devextreme-playwright-tests", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "e2e/playwright", + "projectType": "application", + "targets": { + "lint": { + "executor": "nx:run-script", + "options": { + "script": "lint" + }, + "inputs": [ + "{projectRoot}/**/*.mjs", + "{projectRoot}/**/*.ts" + ] + }, + "test": { + "executor": "nx:run-script", + "options": { + "script": "test" + }, + "inputs": [ + "^default", + "{projectRoot}/helpers/**/*", + "{projectRoot}/models/**/*", + "{projectRoot}/tests/**/*", + "{projectRoot}/playwright.config.ts", + "{projectRoot}/runner.mjs", + "{projectRoot}/tsconfig.json" + ] + } + }, + "tags": [] +} diff --git a/e2e/playwright/runner.mjs b/e2e/playwright/runner.mjs new file mode 100644 index 000000000000..078da053d01e --- /dev/null +++ b/e2e/playwright/runner.mjs @@ -0,0 +1,63 @@ +/* eslint-disable spellcheck/spell-checker */ +import { spawnSync } from 'node:child_process'; +import process from 'node:process'; +import { parseArgs } from 'node:util'; + +const { values, positionals } = parseArgs({ + args: process.argv.slice(2), + allowPositionals: true, + strict: false, + options: { + componentFolder: { type: 'string', default: '' }, + indices: { type: 'string', default: '' }, + theme: { type: 'string', default: 'fluent.blue.light' }, + concurrency: { type: 'string', default: '' }, + test: { type: 'string', default: '' }, + updateSnapshots: { type: 'boolean', default: false }, + }, +}); + +// The matrix already covers every component folder, but the tests are migrated squad by squad, +// so a job whose folder is still TestCafe-only has nothing to run and must not fail. +const args = ['--pass-with-no-tests']; + +if(values.componentFolder) { + args.push(`tests/${values.componentFolder}/`); +} + +if(values.indices) { + const [current, total] = values.indices.split(/[_/\\]|of/i); + + if(!current || !total) { + console.error(`❌ Invalid --indices: ${values.indices}. Pass it as "current/total", for example --indices 1/4.`); + process.exit(1); + } + + args.push(`--shard=${current}/${total}`); +} + +if(values.concurrency) { + args.push(`--workers=${values.concurrency}`); +} + +if(values.test) { + args.push('--grep', values.test); +} + +if(values.updateSnapshots) { + args.push('--update-snapshots'); +} + +args.push(...positionals); + +const { status } = spawnSync( + 'playwright', + ['test', ...args], + { + stdio: 'inherit', + shell: process.platform === 'win32', + env: { ...process.env, THEME: values.theme }, + }, +); + +process.exit(status ?? 1); diff --git a/e2e/playwright/serve.mjs b/e2e/playwright/serve.mjs new file mode 100644 index 000000000000..daa16055c056 --- /dev/null +++ b/e2e/playwright/serve.mjs @@ -0,0 +1,61 @@ +import fs from 'node:fs'; +import http from 'node:http'; +import path from 'node:path'; +import process from 'node:process'; +import { parseArgs } from 'node:util'; + +const MIME_TYPES = { + '.css': 'text/css', + '.html': 'text/html', + '.js': 'text/javascript', + '.json': 'application/json', + '.mjs': 'text/javascript', + '.png': 'image/png', + '.svg': 'image/svg+xml', + '.ttf': 'font/ttf', + '.woff': 'font/woff', + '.woff2': 'font/woff2', +}; + +const { values } = parseArgs({ + options: { + port: { type: 'string' }, + }, +}); + +const port = Number(values.port); + +if(!Number.isInteger(port) || port <= 0) { + console.error(`❌ Invalid port: ${values.port}. Pass a positive integer, for example --port=8080.`); + process.exit(1); +} + +// The test page pulls the built library and the themes straight out of the repository, +// so the whole repository is the document root. +const root = path.resolve(import.meta.dirname, '../..'); +const bundlePath = path.join(root, 'packages/devextreme/artifacts/js/dx.all.js'); + +if(!fs.existsSync(bundlePath)) { + console.error(`❌ DevExtreme build is not found at ${bundlePath}. Run "pnpm exec nx build devextreme -c testing" first.`); + process.exit(1); +} + +const server = http.createServer((request, response) => { + const { pathname } = new URL(request.url, `http://localhost:${port}`); + const filePath = path.join(root, decodeURIComponent(pathname)); + + if(!filePath.startsWith(root) || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { + response.writeHead(404).end('Not found'); + return; + } + + response.writeHead(200, { + 'Content-Type': MIME_TYPES[path.extname(filePath)] ?? 'application/octet-stream', + 'Cache-Control': 'no-store', + }); + fs.createReadStream(filePath).pipe(response); +}); + +server.listen(port, () => { + console.log(`✅ Test pages are served at http://localhost:${port}`); +}); diff --git a/e2e/playwright/tests/container.html b/e2e/playwright/tests/container.html new file mode 100644 index 000000000000..32c6aa58fa88 --- /dev/null +++ b/e2e/playwright/tests/container.html @@ -0,0 +1,34 @@ + + + + Playwright Tests Container + + + + + + + + + + + + + + + + + +
+

Test header

+ +
+
+
+
+
+ + diff --git a/e2e/playwright/tests/editors/checkBox/common.spec.ts b/e2e/playwright/tests/editors/checkBox/common.spec.ts new file mode 100644 index 000000000000..b34ce4af6178 --- /dev/null +++ b/e2e/playwright/tests/editors/checkBox/common.spec.ts @@ -0,0 +1,24 @@ +import { expect, test } from '../../../fixtures'; +import { createWidget } from '../../../helpers/createWidget'; +import CheckBox from '../../../models/checkBox'; + +test('CheckBox switches its state on click', async ({ page }) => { + await createWidget(page, 'dxCheckBox', { value: false }); + + const checkBox = new CheckBox(page, '#container'); + + expect(await checkBox.isChecked()).toBe(false); + + await checkBox.element.click(); + + expect(await checkBox.isChecked()).toBe(true); + expect(await checkBox.option('value')).toBe(true); +}); + +test('CheckBox renders the indeterminate state', async ({ page }) => { + await createWidget(page, 'dxCheckBox', { value: null }); + + const checkBox = new CheckBox(page, '#container'); + + expect(await checkBox.isIndeterminate()).toBe(true); +}); diff --git a/e2e/playwright/tests/navigation/button/common.spec.ts b/e2e/playwright/tests/navigation/button/common.spec.ts new file mode 100644 index 000000000000..60af235c7580 --- /dev/null +++ b/e2e/playwright/tests/navigation/button/common.spec.ts @@ -0,0 +1,71 @@ +import type { ButtonType } from 'devextreme/common'; +import { expect, test } from '../../../fixtures'; +import { createWidget } from '../../../helpers/createWidget'; +import { + addCaptionTo, + appendElementTo, + setAttribute, + setClassAttribute, +} from '../../../helpers/domUtils'; +import { testScreenshot } from '../../../helpers/screenshots'; +import Button from '../../../models/button'; + +const STATES = ['default', 'focused', 'hover', 'active', 'selected', 'disabled']; + +const BUTTONS = [ + ...['danger', 'default', 'normal', 'success'].map((type: ButtonType) => ({ + type, + text: `${type[0].toUpperCase()}${type.slice(1)}`, + })), + { icon: 'find', text: 'Find' }, + { icon: 'find' }, +]; + +['text', 'outlined', 'contained'].forEach((stylingMode) => { + const testName = `Buttons, stylingMode=${stylingMode}`; + + test(testName, async ({ page }) => { + await setAttribute(page, '#container', 'class', 'dx-theme-generic-typography'); + await setAttribute(page, '#container', 'style', 'width: fit-content; padding: 8px;'); + + for (const state of STATES) { + await appendElementTo(page, '#container', 'div', `mode${state}`); + await setAttribute(page, `#mode${state}`, 'style', 'display: flex; gap: 8px; margin-bottom: 16px;'); + await addCaptionTo(page, `#mode${state}`, state); + + for (const [index] of BUTTONS.entries()) { + await appendElementTo(page, `#mode${state}`, 'div', `button-${state}-${index}`); + } + + for (const [index, config] of BUTTONS.entries()) { + await createWidget(page, 'dxButton', { + ...config, + stylingMode, + disabled: state === 'disabled', + }, `#button-${state}-${index}`); + } + + if (state !== 'default' && state !== 'disabled') { + for (const [index] of BUTTONS.entries()) { + await setClassAttribute(page, `#button-${state}-${index}`, `dx-state-${state}`); + } + } + } + + await testScreenshot(page, `${testName}.png`); + }); +}); + +test('Button reports its text, selected and disabled state', async ({ page }) => { + await createWidget(page, 'dxButton', { text: 'Find', icon: 'find' }); + + const button = new Button(page, '#container'); + + await expect(button.text).toHaveText('Find'); + expect(await button.isSelected()).toBe(false); + expect(await button.isDisabled()).toBe(false); + + await button.option('disabled', true); + + expect(await button.isDisabled()).toBe(true); +}); diff --git a/e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=contained (fluent.blue.light).png b/e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=contained (fluent.blue.light).png new file mode 100644 index 0000000000000000000000000000000000000000..ce153a4cb49496b3c485784f0ae60312c72cd297 GIT binary patch literal 36994 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 b#0LWARw_uY<#sJQ8-R_agGHs8_lvKze literal 0 HcmV?d00001 diff --git a/e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=outlined (fluent.blue.light).png b/e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=outlined (fluent.blue.light).png new file mode 100644 index 0000000000000000000000000000000000000000..2ee920bbf97b063fa99e5412e3e25b8eac21d229 GIT binary patch literal 37094 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 v|99i|zMw%szP@N52 Date: Thu, 27 Aug 2026 13:37:51 -0300 Subject: [PATCH 12/22] Fall back to the default theme and timezone when the matrix leaves them empty --- e2e/playwright/helpers/const.ts | 5 +++++ e2e/playwright/helpers/themeUtils.ts | 4 ++-- e2e/playwright/playwright.config.ts | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/e2e/playwright/helpers/const.ts b/e2e/playwright/helpers/const.ts index 7dafa3374eca..be1b7a492d81 100644 --- a/e2e/playwright/helpers/const.ts +++ b/e2e/playwright/helpers/const.ts @@ -7,3 +7,8 @@ export const DEFAULT_SELECTOR = '#container'; export const SERVER_PORT = 8080; export const TEST_PAGE_URL = '/e2e/playwright/tests/container.html'; + +// A matrix input that is not set for the job arrives as an empty string, not as undefined. +export const readEnv = (value: string | undefined, fallback: string): string => ( + value === undefined || value === '' ? fallback : value +); diff --git a/e2e/playwright/helpers/themeUtils.ts b/e2e/playwright/helpers/themeUtils.ts index 143c2d98f2ef..6eb3002ee79a 100644 --- a/e2e/playwright/helpers/themeUtils.ts +++ b/e2e/playwright/helpers/themeUtils.ts @@ -1,6 +1,6 @@ -import { DEFAULT_THEME } from './const'; +import { DEFAULT_THEME, readEnv } from './const'; -export const getFullThemeName = (): string => process.env.THEME ?? DEFAULT_THEME; +export const getFullThemeName = (): string => readEnv(process.env.THEME, DEFAULT_THEME); export const getThemeName = (): string => getFullThemeName().split('.')[0]; diff --git a/e2e/playwright/playwright.config.ts b/e2e/playwright/playwright.config.ts index 00f9c7c3bc1e..6c5034a2eef1 100644 --- a/e2e/playwright/playwright.config.ts +++ b/e2e/playwright/playwright.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from '@playwright/test'; import type { TestOptions } from './fixtures'; import { - DEFAULT_BROWSER_SIZE, DEFAULT_THEME, SERVER_PORT, TEST_PAGE_URL, + DEFAULT_BROWSER_SIZE, DEFAULT_THEME, readEnv, SERVER_PORT, TEST_PAGE_URL, } from './helpers/const'; const baseURL = `http://localhost:${SERVER_PORT}`; @@ -29,10 +29,10 @@ export default defineConfig({ toHaveScreenshot: screenshotBudget, }, use: { - theme: process.env.THEME ?? DEFAULT_THEME, + theme: readEnv(process.env.THEME, DEFAULT_THEME), // The TestCafe run changes the machine timezone on the CI agent; the browser context // takes it per run instead, so the same agent can serve any timezone job. - timezoneId: process.env.TIMEZONE ?? 'GMT', + timezoneId: readEnv(process.env.TIMEZONE, 'GMT'), baseURL, channel: 'chrome', headless: true, From 2c842402820ef125a7bce95f3451cac55fcf2ec9 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Thu, 27 Aug 2026 13:39:35 -0300 Subject: [PATCH 13/22] Lint the new package in the lint workflow and keep its e2e run out of the default one --- .github/workflows/default_workflow.yml | 1 + .github/workflows/lint.yml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/default_workflow.yml b/.github/workflows/default_workflow.yml index c49d07553117..0c0581db5a7e 100644 --- a/.github/workflows/default_workflow.yml +++ b/.github/workflows/default_workflow.yml @@ -108,6 +108,7 @@ jobs: devextreme-react-storybook devextreme-angular-playground devextreme-testcafe-tests + devextreme-playwright-tests devextreme-demos devextreme-react-playground devextreme-vue-playground diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 786e7ae848fb..525ce9fa82c8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -115,6 +115,12 @@ jobs: DEBUG: eslint:cli-engine run: pnpm exec nx lint + - name: Lint Playwright tests + working-directory: ./e2e/playwright + env: + DEBUG: eslint:cli-engine + run: pnpm exec nx lint + JS: runs-on: devextreme-shr2 timeout-minutes: 60 From c8772f5173105c1fe61168ca865734e79d4132a8 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Thu, 27 Aug 2026 13:53:35 -0300 Subject: [PATCH 14/22] Run a theme job only on the tests that opted into that theme --- e2e/playwright/README.md | 12 ++++++++++++ e2e/playwright/runner.mjs | 11 +++++++++-- e2e/playwright/tests/editors/checkBox/common.spec.ts | 7 +++++-- .../tests/navigation/button/common.spec.ts | 5 ++++- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/e2e/playwright/README.md b/e2e/playwright/README.md index 35b78a12809b..82282dd62fb0 100644 --- a/e2e/playwright/README.md +++ b/e2e/playwright/README.md @@ -43,6 +43,18 @@ docker/run.sh # verify docker/run.sh --update-snapshots # rewrite the etalons of the tests that ran ``` +## Themes + +A test runs in the default theme unless it says otherwise. The jobs that run the whole suite in +another theme (`generic`, `material`, `material - compact`) take only the tests tagged with that +theme — the same opt-in the TestCafe `meta.themes` gives: + +```ts +test('CheckBox switches its state on click', { tag: ['@generic.light'] }, async ({ page }) => { +``` + +Tag a test when its result does not depend on the theme, or when it has an etalon for that theme. + ## Run in the CI environment The container takes Node from `.node-version` and Chrome from the version the workflow installs; diff --git a/e2e/playwright/runner.mjs b/e2e/playwright/runner.mjs index 078da053d01e..e1a20198bac6 100644 --- a/e2e/playwright/runner.mjs +++ b/e2e/playwright/runner.mjs @@ -3,6 +3,8 @@ import { spawnSync } from 'node:child_process'; import process from 'node:process'; import { parseArgs } from 'node:util'; +const DEFAULT_THEME = 'fluent.blue.light'; + const { values, positionals } = parseArgs({ args: process.argv.slice(2), allowPositionals: true, @@ -10,7 +12,7 @@ const { values, positionals } = parseArgs({ options: { componentFolder: { type: 'string', default: '' }, indices: { type: 'string', default: '' }, - theme: { type: 'string', default: 'fluent.blue.light' }, + theme: { type: 'string' }, concurrency: { type: 'string', default: '' }, test: { type: 'string', default: '' }, updateSnapshots: { type: 'boolean', default: false }, @@ -42,6 +44,11 @@ if(values.concurrency) { if(values.test) { args.push('--grep', values.test); +} else if(values.theme && !values.componentFolder) { + // A job that names a theme instead of a folder runs the whole suite in that theme, so it + // takes only the tests that opted into it with a "@" tag — the etalons of the rest + // are recorded in the default theme. + args.push('--grep', `@${values.theme}`); } if(values.updateSnapshots) { @@ -56,7 +63,7 @@ const { status } = spawnSync( { stdio: 'inherit', shell: process.platform === 'win32', - env: { ...process.env, THEME: values.theme }, + env: { ...process.env, THEME: values.theme ?? DEFAULT_THEME }, }, ); diff --git a/e2e/playwright/tests/editors/checkBox/common.spec.ts b/e2e/playwright/tests/editors/checkBox/common.spec.ts index b34ce4af6178..e49f1ec699ab 100644 --- a/e2e/playwright/tests/editors/checkBox/common.spec.ts +++ b/e2e/playwright/tests/editors/checkBox/common.spec.ts @@ -2,7 +2,10 @@ import { expect, test } from '../../../fixtures'; import { createWidget } from '../../../helpers/createWidget'; import CheckBox from '../../../models/checkBox'; -test('CheckBox switches its state on click', async ({ page }) => { +// The tags list the themes this test also runs in; a test without them runs in the default one. +const THEMES = { tag: ['@generic.light', '@material.blue.light', '@material.blue.light.compact'] }; + +test('CheckBox switches its state on click', THEMES, async ({ page }) => { await createWidget(page, 'dxCheckBox', { value: false }); const checkBox = new CheckBox(page, '#container'); @@ -15,7 +18,7 @@ test('CheckBox switches its state on click', async ({ page }) => { expect(await checkBox.option('value')).toBe(true); }); -test('CheckBox renders the indeterminate state', async ({ page }) => { +test('CheckBox renders the indeterminate state', THEMES, async ({ page }) => { await createWidget(page, 'dxCheckBox', { value: null }); const checkBox = new CheckBox(page, '#container'); diff --git a/e2e/playwright/tests/navigation/button/common.spec.ts b/e2e/playwright/tests/navigation/button/common.spec.ts index 60af235c7580..db45035d09e0 100644 --- a/e2e/playwright/tests/navigation/button/common.spec.ts +++ b/e2e/playwright/tests/navigation/button/common.spec.ts @@ -10,6 +10,9 @@ import { import { testScreenshot } from '../../../helpers/screenshots'; import Button from '../../../models/button'; +// The tags list the themes this test also runs in; a test without them runs in the default one. +const THEMES = { tag: ['@generic.light', '@material.blue.light', '@material.blue.light.compact'] }; + const STATES = ['default', 'focused', 'hover', 'active', 'selected', 'disabled']; const BUTTONS = [ @@ -56,7 +59,7 @@ const BUTTONS = [ }); }); -test('Button reports its text, selected and disabled state', async ({ page }) => { +test('Button reports its text, selected and disabled state', THEMES, async ({ page }) => { await createWidget(page, 'dxButton', { text: 'Find', icon: 'find' }); const button = new Button(page, '#container'); From 2ab615bdbc575386d1e00c713d4fa6363a22b73d Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Thu, 27 Aug 2026 14:31:53 -0300 Subject: [PATCH 15/22] Match the TestCafe semantics in the runner flags, the request mocks and the screenshot target --- e2e/playwright/README.md | 8 ++- e2e/playwright/fixtures.ts | 14 ++-- e2e/playwright/helpers/apiMock.ts | 23 ++++--- e2e/playwright/helpers/screenshots.ts | 8 +-- e2e/playwright/helpers/testPageUtils.ts | 42 ------------ e2e/playwright/models/internal/widget.ts | 6 +- e2e/playwright/runner.mjs | 66 ++++++++++++------- e2e/playwright/serve.mjs | 7 +- .../tests/navigation/button/common.spec.ts | 2 +- 9 files changed, 72 insertions(+), 104 deletions(-) diff --git a/e2e/playwright/README.md b/e2e/playwright/README.md index 82282dd62fb0..a657f2ef5733 100644 --- a/e2e/playwright/README.md +++ b/e2e/playwright/README.md @@ -24,11 +24,13 @@ pnpm run test --test "Buttons, stylingMode=text" # one test by name The runner takes the flags the TestCafe one takes (`--componentFolder`, `--indices`, `--theme`, `--concurrency`, `--test`) and passes everything else straight to `playwright test`, so -`pnpm run test --headed --debug` works too. The static server for the test page starts on its own. +`pnpm run test --headed --repeat-each 3` works too. `--test` matches the name as a whole word, not +as a regular expression. The static server for the test page starts on its own. ## Screenshots -Etalons live in `etalons/` next to the test and carry the theme in the file name — +`testScreenshot` shoots the whole viewport unless the call names an `element`, the way the TestCafe +comparer did. Etalons live in `etalons/` next to the test and carry the theme in the file name — `Buttons, stylingMode=text (fluent.blue.light).png` — the same convention the TestCafe run uses. The pixel budget is asymmetric on purpose: **CI is the source of truth** (`maxDiffPixelRatio` @@ -95,7 +97,7 @@ test('Button reports its text', async ({ page }) => { const button = new Button(page, '#container .dx-button'); await expect(button.text).toHaveText('Find'); - await testScreenshot(page, 'Button with text.png'); + await testScreenshot(page, 'Button with text.png', { element: '#container' }); }); ``` diff --git a/e2e/playwright/fixtures.ts b/e2e/playwright/fixtures.ts index 42e03d50e0db..715d8fab0e83 100644 --- a/e2e/playwright/fixtures.ts +++ b/e2e/playwright/fixtures.ts @@ -1,19 +1,15 @@ import { test as base } from '@playwright/test'; import { DEFAULT_BROWSER_SIZE, DEFAULT_THEME } from './helpers/const'; -import { - changeTheme, - clearTestPage, - openTestPage, - resetPageState, -} from './helpers/testPageUtils'; +import { changeTheme, openTestPage, resetPageState } from './helpers/testPageUtils'; export interface TestOptions { theme: string; browserSize: [number, number]; } -// Overriding "page" gives every test the prepared container page, the way the TestCafe runner -// did it from its global test hooks. +// Overriding "page" gives every test the prepared container page, the way the TestCafe runner did +// it from its global test hooks. Nothing needs cleaning up afterwards: every test gets its own +// context, and the widgets of the previous one die with its page. export const test = base.extend({ theme: [DEFAULT_THEME, { option: true }], browserSize: [DEFAULT_BROWSER_SIZE, { option: true }], @@ -27,8 +23,6 @@ export const test = base.extend({ await resetPageState(page); await use(page); - - await clearTestPage(page); }, }); diff --git a/e2e/playwright/helpers/apiMock.ts b/e2e/playwright/helpers/apiMock.ts index 85cef2471226..8e367461c9e8 100644 --- a/e2e/playwright/helpers/apiMock.ts +++ b/e2e/playwright/helpers/apiMock.ts @@ -7,17 +7,20 @@ export interface MockedRequest { headers?: Record; } -// Replaces the TestCafe RequestMock: the rules are matched in the order they are declared, -// so a narrower rule has to come before the wider one, just as it did there. +// Replaces the TestCafe RequestMock: the rules are matched in the order they are declared, so a +// narrower rule has to come before the wider one, just as it did there. Registration runs backwards +// because "page.route" gives the last registered handler priority. export async function mockApi(page: Page, requests: MockedRequest[]): Promise { - await Promise.all(requests.map(({ + for (const { url, body, status = 200, headers, - }) => page.route(url, async (route) => { - await route.fulfill({ - status, - contentType: 'application/json', - headers: { 'access-control-allow-origin': '*', ...headers }, - body: JSON.stringify(body), + } of [...requests].reverse()) { + await page.route(url, async (route) => { + await route.fulfill({ + status, + contentType: 'application/json', + headers: { 'access-control-allow-origin': '*', ...headers }, + body: JSON.stringify(body), + }); }); - }))); + } } diff --git a/e2e/playwright/helpers/screenshots.ts b/e2e/playwright/helpers/screenshots.ts index 01971f99f69d..1ca4fd6ea844 100644 --- a/e2e/playwright/helpers/screenshots.ts +++ b/e2e/playwright/helpers/screenshots.ts @@ -1,6 +1,5 @@ import type { Locator, Page } from '@playwright/test'; import { expect } from '@playwright/test'; -import { DEFAULT_SELECTOR } from './const'; import { getFullThemeName, getThemePostfix } from './themeUtils'; import { changeTheme } from './testPageUtils'; @@ -22,19 +21,16 @@ const getScreenshotName = (baseName: string, theme?: string): string => { : `${baseName}${themePostfix}.png`; }; +// No element means the whole viewport, the way the TestCafe comparer read a missing element. const resolveTarget = ( page: Page, element: Locator | string | null | undefined, ): Locator | Page => { - if (element === null) { - return page; - } - if (typeof element === 'string') { return page.locator(element); } - return element ?? page.locator(DEFAULT_SELECTOR); + return element ?? page; }; export async function testScreenshot( diff --git a/e2e/playwright/helpers/testPageUtils.ts b/e2e/playwright/helpers/testPageUtils.ts index 3fe744e79eaf..a944a3671625 100644 --- a/e2e/playwright/helpers/testPageUtils.ts +++ b/e2e/playwright/helpers/testPageUtils.ts @@ -27,48 +27,6 @@ export async function getCurrentTheme(page: Page): Promise { return theme ?? DEFAULT_THEME; } -export async function clearTestPage(page: Page): Promise { - await page.evaluate(() => { - const widgetSelector = '.dx-widget'; - const $elements = $(widgetSelector) - .filter((_, element) => $(element).parents(widgetSelector).length === 0); - - $elements.each((_, element) => { - const $widgetElement = $(element); - const widgetNames = $widgetElement.data().dxComponents; - - widgetNames?.forEach((name) => { - if ($widgetElement.hasClass('dx-widget')) { - ($widgetElement as any)[name]?.('dispose'); - } - }); - $widgetElement.empty(); - }); - - document.getElementById('focusable-start')?.remove(); - document.getElementById('stylesheetRules')?.remove(); - - const body = document.querySelector('body'); - - if (body) { - body.innerHTML = ''; - body.className = 'dx-surface'; - } - - const temp = document.createElement('div'); - - temp.innerHTML = ` -
-

Test header

-
-
-
- `; - - body?.prepend(temp.firstElementChild!); - }); -} - export async function resetPageState(page: Page): Promise { await page.evaluate(() => { if (document.activeElement && document.activeElement !== document.body) { diff --git a/e2e/playwright/models/internal/widget.ts b/e2e/playwright/models/internal/widget.ts index 5248e39ae630..9fe9f20177d8 100644 --- a/e2e/playwright/models/internal/widget.ts +++ b/e2e/playwright/models/internal/widget.ts @@ -47,11 +47,7 @@ export default abstract class Widget { } public async option(option: string | Record, ...value: unknown[]): Promise { - const isSetter = value.length > 0 || typeof option === 'object'; - - return isSetter - ? this.invoke('option', option, ...value) - : this.invoke('option', option); + return this.invoke('option', option, ...value); } public async focus(): Promise { diff --git a/e2e/playwright/runner.mjs b/e2e/playwright/runner.mjs index e1a20198bac6..f42c06f597eb 100644 --- a/e2e/playwright/runner.mjs +++ b/e2e/playwright/runner.mjs @@ -1,27 +1,44 @@ -/* eslint-disable spellcheck/spell-checker */ + import { spawnSync } from 'node:child_process'; import process from 'node:process'; -import { parseArgs } from 'node:util'; const DEFAULT_THEME = 'fluent.blue.light'; -const { values, positionals } = parseArgs({ - args: process.argv.slice(2), - allowPositionals: true, - strict: false, - options: { - componentFolder: { type: 'string', default: '' }, - indices: { type: 'string', default: '' }, - theme: { type: 'string' }, - concurrency: { type: 'string', default: '' }, - test: { type: 'string', default: '' }, - updateSnapshots: { type: 'boolean', default: false }, - }, -}); +const OPTIONS = ['componentFolder', 'indices', 'theme', 'concurrency', 'test']; + +const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + +// Everything this runner does not know about goes to "playwright test" untouched, so +// "pnpm run test --headed --repeat-each 3" behaves exactly as the Playwright CLI would. +function parseArgs(argv) { + const values = {}; + const rest = []; + + for(let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + const [flag, inlineValue] = arg.startsWith('--') ? arg.slice(2).split(/=(.*)/s) : []; + + if(OPTIONS.includes(flag)) { + if(inlineValue === undefined) { + i += 1; + values[flag] = argv[i]; + } else { + values[flag] = inlineValue; + } + } else { + rest.push(arg); + } + } + + return { values, rest }; +} + +const { values, rest } = parseArgs(process.argv.slice(2)); // The matrix already covers every component folder, but the tests are migrated squad by squad, -// so a job whose folder is still TestCafe-only has nothing to run and must not fail. -const args = ['--pass-with-no-tests']; +// so a job whose folder is still TestCafe-only has nothing to run and must not fail. A run asking +// for one test by name is the opposite case: matching nothing there means the name is wrong. +const args = values.test ? [] : ['--pass-with-no-tests']; if(values.componentFolder) { args.push(`tests/${values.componentFolder}/`); @@ -43,19 +60,18 @@ if(values.concurrency) { } if(values.test) { - args.push('--grep', values.test); + // Playwright greps the title with the tags appended, so the name can only be bounded by + // whitespace, not anchored. Escaping matters: test names here carry ticket numbers in braces. + args.push('--grep', `(^|\\s)${escapeRegExp(values.test)}(\\s|$)`); } else if(values.theme && !values.componentFolder) { // A job that names a theme instead of a folder runs the whole suite in that theme, so it // takes only the tests that opted into it with a "@" tag — the etalons of the rest - // are recorded in the default theme. - args.push('--grep', `@${values.theme}`); -} - -if(values.updateSnapshots) { - args.push('--update-snapshots'); + // are recorded in the default theme. The tag ends at a space, otherwise "@material.blue.light" + // would also pick up the tests tagged "@material.blue.light.compact". + args.push('--grep', `@${escapeRegExp(values.theme)}(\\s|$)`); } -args.push(...positionals); +args.push(...rest); const { status } = spawnSync( 'playwright', diff --git a/e2e/playwright/serve.mjs b/e2e/playwright/serve.mjs index daa16055c056..57fe135789c1 100644 --- a/e2e/playwright/serve.mjs +++ b/e2e/playwright/serve.mjs @@ -43,8 +43,11 @@ if(!fs.existsSync(bundlePath)) { const server = http.createServer((request, response) => { const { pathname } = new URL(request.url, `http://localhost:${port}`); const filePath = path.join(root, decodeURIComponent(pathname)); + // A prefix check would also accept a sibling directory whose name starts with the root one. + const relative = path.relative(root, filePath); + const isInsideRoot = relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative); - if(!filePath.startsWith(root) || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { + if(!isInsideRoot || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { response.writeHead(404).end('Not found'); return; } @@ -56,6 +59,6 @@ const server = http.createServer((request, response) => { fs.createReadStream(filePath).pipe(response); }); -server.listen(port, () => { +server.listen(port, '127.0.0.1', () => { console.log(`✅ Test pages are served at http://localhost:${port}`); }); diff --git a/e2e/playwright/tests/navigation/button/common.spec.ts b/e2e/playwright/tests/navigation/button/common.spec.ts index db45035d09e0..e192ae28a3d0 100644 --- a/e2e/playwright/tests/navigation/button/common.spec.ts +++ b/e2e/playwright/tests/navigation/button/common.spec.ts @@ -55,7 +55,7 @@ const BUTTONS = [ } } - await testScreenshot(page, `${testName}.png`); + await testScreenshot(page, `${testName}.png`, { element: '#container' }); }); }); From 9f0bf21fa4a3a7259483debb6500e56ed46672f3 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Thu, 27 Aug 2026 15:35:29 -0300 Subject: [PATCH 16/22] Name the workflow after Playwright, accept the "1 of 4" shard form and stop doubling the compact suffix --- ...testcafe_tests_playwright.yml => playwright_tests.yml} | 0 e2e/playwright/docker/run.sh | 2 +- e2e/playwright/helpers/screenshots.ts | 8 +++++--- e2e/playwright/runner.mjs | 5 +++-- 4 files changed, 9 insertions(+), 6 deletions(-) rename .github/workflows/{testcafe_tests_playwright.yml => playwright_tests.yml} (100%) diff --git a/.github/workflows/testcafe_tests_playwright.yml b/.github/workflows/playwright_tests.yml similarity index 100% rename from .github/workflows/testcafe_tests_playwright.yml rename to .github/workflows/playwright_tests.yml diff --git a/e2e/playwright/docker/run.sh b/e2e/playwright/docker/run.sh index 006ed53f6020..7334c2cf1ab0 100755 --- a/e2e/playwright/docker/run.sh +++ b/e2e/playwright/docker/run.sh @@ -12,7 +12,7 @@ fi REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" NODE_VERSION="$(cat "$REPO_ROOT/.node-version")" -CHROME_VERSION="$(grep -m1 "chrome-version:" "$REPO_ROOT/.github/workflows/testcafe_tests_playwright.yml" | cut -d: -f2 | tr -d " '\"")" +CHROME_VERSION="$(grep -m1 "chrome-version:" "$REPO_ROOT/.github/workflows/playwright_tests.yml" | cut -d: -f2 | tr -d " '\"")" echo "Node $NODE_VERSION, Google Chrome $CHROME_VERSION" diff --git a/e2e/playwright/helpers/screenshots.ts b/e2e/playwright/helpers/screenshots.ts index 1ca4fd6ea844..cf8a2aa3a19b 100644 --- a/e2e/playwright/helpers/screenshots.ts +++ b/e2e/playwright/helpers/screenshots.ts @@ -56,12 +56,14 @@ export async function testScreenshot( await expect(target).toHaveScreenshot([getScreenshotName(screenshotName, theme)]); if (shouldTestInCompact) { - const themeName = theme ?? getFullThemeName(); + // The theme of a "- compact" job already ends with the suffix; appending it twice would ask + // for a theme that does not exist. + const compactTheme = `${(theme ?? getFullThemeName()).replace(/\.compact$/, '')}.compact`; - await changeTheme(page, `${themeName}.compact`); + await changeTheme(page, compactTheme); await compactCallBack?.(); - await expect(target).toHaveScreenshot([getScreenshotName(screenshotName, `${themeName}.compact`)]); + await expect(target).toHaveScreenshot([getScreenshotName(screenshotName, compactTheme)]); } if (theme || shouldTestInCompact) { diff --git a/e2e/playwright/runner.mjs b/e2e/playwright/runner.mjs index f42c06f597eb..57ba4af981d3 100644 --- a/e2e/playwright/runner.mjs +++ b/e2e/playwright/runner.mjs @@ -45,9 +45,10 @@ if(values.componentFolder) { } if(values.indices) { - const [current, total] = values.indices.split(/[_/\\]|of/i); + // "1/4", "1_4" and "1 of 4" all mean the same shard, the way the TestCafe runner read them. + const [current, total] = values.indices.split(/[_/\\]|of/i).map((part) => Number(part)); - if(!current || !total) { + if(!Number.isInteger(current) || !Number.isInteger(total) || current < 1 || total < 1) { console.error(`❌ Invalid --indices: ${values.indices}. Pass it as "current/total", for example --indices 1/4.`); process.exit(1); } From db2db0f8745f9f77e6ad8b3af03c6ef19ad60d23 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Thu, 27 Aug 2026 23:04:01 -0300 Subject: [PATCH 17/22] Use PivotGrid as the reference component instead of widgets another squad owns --- e2e/playwright/README.md | 24 +++--- e2e/playwright/models/button.ts | 24 ------ e2e/playwright/models/checkBox.ts | 23 ------ e2e/playwright/models/pivotGrid.ts | 26 ++++++ .../PivotGrid markup (fluent.blue.light).png | Bin 0 -> 11619 bytes .../tests/common/pivotGrid/reference.spec.ts | 42 ++++++++++ .../tests/editors/checkBox/common.spec.ts | 27 ------- .../tests/navigation/button/common.spec.ts | 74 ------------------ ...lingMode=contained (fluent.blue.light).png | Bin 36994 -> 0 bytes ...ylingMode=outlined (fluent.blue.light).png | Bin 37094 -> 0 bytes ..., stylingMode=text (fluent.blue.light).png | Bin 33951 -> 0 bytes 11 files changed, 80 insertions(+), 160 deletions(-) delete mode 100644 e2e/playwright/models/button.ts delete mode 100644 e2e/playwright/models/checkBox.ts create mode 100644 e2e/playwright/models/pivotGrid.ts create mode 100644 e2e/playwright/tests/common/pivotGrid/etalons/PivotGrid markup (fluent.blue.light).png create mode 100644 e2e/playwright/tests/common/pivotGrid/reference.spec.ts delete mode 100644 e2e/playwright/tests/editors/checkBox/common.spec.ts delete mode 100644 e2e/playwright/tests/navigation/button/common.spec.ts delete mode 100644 e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=contained (fluent.blue.light).png delete mode 100644 e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=outlined (fluent.blue.light).png delete mode 100644 e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=text (fluent.blue.light).png diff --git a/e2e/playwright/README.md b/e2e/playwright/README.md index a657f2ef5733..65401290f81b 100644 --- a/e2e/playwright/README.md +++ b/e2e/playwright/README.md @@ -16,10 +16,10 @@ pnpm exec nx build devextreme -c testing cd e2e/playwright pnpm run test # everything, fluent.blue.light -pnpm run test --componentFolder navigation # one component folder +pnpm run test --componentFolder common/pivotGrid # one component folder pnpm run test --theme material.blue.light # another theme pnpm run test --indices 1/4 --concurrency 5 # one shard, five workers -pnpm run test --test "Buttons, stylingMode=text" # one test by name +pnpm run test --test "PivotGrid markup" # one test by name ``` The runner takes the flags the TestCafe one takes (`--componentFolder`, `--indices`, `--theme`, @@ -31,7 +31,7 @@ as a regular expression. The static server for the test page starts on its own. `testScreenshot` shoots the whole viewport unless the call names an `element`, the way the TestCafe comparer did. Etalons live in `etalons/` next to the test and carry the theme in the file name — -`Buttons, stylingMode=text (fluent.blue.light).png` — the same convention the TestCafe run uses. +`PivotGrid markup (fluent.blue.light).png` — the same convention the TestCafe run uses. The pixel budget is asymmetric on purpose: **CI is the source of truth** (`maxDiffPixelRatio` 0.001, `threshold` 0.1) and a local run is a sanity check (0.05 / 0.2). @@ -52,7 +52,7 @@ another theme (`generic`, `material`, `material - compact`) take only the tests theme — the same opt-in the TestCafe `meta.themes` gives: ```ts -test('CheckBox switches its state on click', { tag: ['@generic.light'] }, async ({ page }) => { +test('PivotGrid renders its areas', { tag: ['@generic.light'] }, async ({ page }) => { ``` Tag a test when its result does not depend on the theme, or when it has an etalon for that theme. @@ -73,8 +73,8 @@ its trace viewer will not load there. ## Read a failed CI run ```bash -gh run download -n playwright-report-navigation -D ci-report -gh run download -n playwright-traces-navigation -D ci-traces +gh run download -n playwright-report-common -D ci-report +gh run download -n playwright-traces-common -D ci-traces pnpm exec playwright show-report ci-report pnpm exec playwright show-trace ci-traces//trace.zip --port 0 ``` @@ -89,15 +89,15 @@ failure that only happens on CI can be diagnosed without reproducing it. import { expect, test } from '../../../fixtures'; import { createWidget } from '../../../helpers/createWidget'; import { testScreenshot } from '../../../helpers/screenshots'; -import Button from '../../../models/button'; +import PivotGrid from '../../../models/pivotGrid'; -test('Button reports its text', async ({ page }) => { - await createWidget(page, 'dxButton', { text: 'Find' }); +test('PivotGrid renders its data area', async ({ page }) => { + await createWidget(page, 'dxPivotGrid', { dataSource: { store: [] } }); - const button = new Button(page, '#container .dx-button'); + const pivotGrid = new PivotGrid(page, '#container'); - await expect(button.text).toHaveText('Find'); - await testScreenshot(page, 'Button with text.png', { element: '#container' }); + await expect(pivotGrid.dataArea).toBeVisible(); + await testScreenshot(page, 'PivotGrid markup.png', { element: '#container' }); }); ``` diff --git a/e2e/playwright/models/button.ts b/e2e/playwright/models/button.ts deleted file mode 100644 index 81df6f59b1d2..000000000000 --- a/e2e/playwright/models/button.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Locator } from '@playwright/test'; -import type { WidgetName } from './types'; -import Widget from './internal/widget'; - -export const CLASS = { - text: '.dx-button-text', - selected: 'dx-state-selected', - itemSelected: 'dx-item-selected', -}; - -export default class Button extends Widget { - public static className = 'dx-button'; - - public get text(): Locator { - return this.element.locator(CLASS.text); - } - - public isSelected(): Promise { - return this.hasClass(CLASS.selected); - } - - // eslint-disable-next-line class-methods-use-this - public getName(): WidgetName { return 'dxButton'; } -} diff --git a/e2e/playwright/models/checkBox.ts b/e2e/playwright/models/checkBox.ts deleted file mode 100644 index 0ba7c189b705..000000000000 --- a/e2e/playwright/models/checkBox.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { WidgetName } from './types'; -import Widget from './internal/widget'; - -export const CLASS = { - checked: 'dx-checkbox-checked', - indeterminate: 'dx-checkbox-indeterminate', - icon: '.dx-checkbox-icon', -}; - -export default class CheckBox extends Widget { - public static className = 'dx-checkbox'; - - public isChecked(): Promise { - return this.hasClass(CLASS.checked); - } - - public isIndeterminate(): Promise { - return this.hasClass(CLASS.indeterminate); - } - - // eslint-disable-next-line class-methods-use-this - public getName(): WidgetName { return 'dxCheckBox'; } -} diff --git a/e2e/playwright/models/pivotGrid.ts b/e2e/playwright/models/pivotGrid.ts new file mode 100644 index 000000000000..c32e74e9a93e --- /dev/null +++ b/e2e/playwright/models/pivotGrid.ts @@ -0,0 +1,26 @@ +import type { Locator } from '@playwright/test'; +import type { WidgetName } from './types'; +import Widget from './internal/widget'; + +export const CLASS = { + area: '.dx-pivotgrid-area', + dataArea: '.dx-pivotgrid-area-data', + fieldChooserButton: '.dx-pivotgrid-field-chooser-button', +}; + +// The reference model of the package: it shows what a squad writes on top of Widget — a Locator +// per part of the markup and the widget API through option()/invoke(). +export default class PivotGrid extends Widget { + public static className = 'dx-pivotgrid'; + + public get dataArea(): Locator { + return this.element.locator(CLASS.dataArea); + } + + public getAreaCell(area: string, index: number): Locator { + return this.element.locator(`${CLASS.area}-${area} td`).nth(index); + } + + // eslint-disable-next-line class-methods-use-this + public getName(): WidgetName { return 'dxPivotGrid'; } +} diff --git a/e2e/playwright/tests/common/pivotGrid/etalons/PivotGrid markup (fluent.blue.light).png b/e2e/playwright/tests/common/pivotGrid/etalons/PivotGrid markup (fluent.blue.light).png new file mode 100644 index 0000000000000000000000000000000000000000..7c0e1a974479537c85e809e923381e2783796f07 GIT binary patch literal 11619 zcmcJVcT^MIzOYeLL_|cIh=78qfHXs|FVa!Egc3p&q=sGt7*G)q5RfiidJhPM5(uD3 zFQJ9rdngGl)BqRWbIx7se(O8mU3abf{ga(!_RQWhduGq`dwx4uTT_Ml#=RS4WMtH8 zs*1W~WLJKif3LoN`TP^KMShEn>^7O2qWo)E^7HXY7cu^ z=h;O!n;o?iwMi$6WgmZiPDqkiZ`uYSJw7_K-}uOeWfMcIUj$=e>R~bLqVJpmBTi z;%DRI>#&RR4c!&#i?WpRGTTKta^(`uMQJxDW;WS%I2$R0W6+nM)8YR! z>S8k)>E00g=5Bi^JDkoyY%Jv%Fxzc%51MZ+BbQ>7hPZxFrLpAA2}sx-44C9rlWP!u z()`%saZP~D#YH{eHqTvYEz4|W2{*{O_=AjO?Cab(kPCWWA5kylyXx+>Offkb%WXG! z<$U7@t92$hB|V67Z!&@S{v@LH5VedpwD&M&YQ*#rDi#{H_L{wRW@#>u6#Z_paI7;D zVPyPpqCM5PlX=LyM<}U51Y5Nov5X!=AE)A`8_V}Y4K`y8tI9Uj+6i)p`Q&p`p%#Zd z__+Gb#WOF`ujdGbu$O8WZW&(=F<&%|j{aQeX}^18dy~P|8!I^EobZb?paH7mOsB20U0?;iq^QSwNFfgqQa*pdh z+5Ql(E3+G6`!PNxi_Q9;Eymp#iW)HBH=dv6kr71{TBB*TLMBr=J}@c6igFeYqNqq&Uw=GNP317D! z?d-P|JvQd(+o`&P1|rwNE?j|fL4<$Z}r}IiEW>C{3H)n z=a$s-e3W8jPAb^N?Szrff^)CchTB2b{@h}d{N`2f4)c)LQq$SR)wjfjnh(vT(pOb& z9wc=555*|Ms-HFJ+pIdO4KrxU6EJvA-6@Ci^2&xW;Li_k=k>$JIHpA}nY8EI zv=`>?P5w#@bUhtU|FS;zv$mS#rdBp|f=`oN`uzQ>rvu zw8-t;X@gh(Iv+(ROW0AzJ8{t`00)a|r=LYdT85oInVD!S58N_uL127F)risj3@%C8 z;dt>B)zyk|O!)P)esmXrR)`m~i8xdg`3#f<`Ny&b6G$6(5$Z=w`iiz7C z`2?e>+Yi%GyQP=LT9Xk|>F0Y$P{>W54-0F08Bymz+z4Ba*X`LoJ6UVkPgbW%Es@lc znYVZsn4`>;J!s~*ooUY8o&t78{ZV(|ZMeR%7ZZuyUCds+slOvOy!Xs1x!!GKEPEi| z*i*kryBFmmexKGHzn!PsKL|Le!gQEB2#X#24+VRynP{|rU0%s9IrW&$TdO>4*^ZXU z`YXv=V)Xg^Huzw5%pgT}cq4plX-zq!q})D@*Ag;-Jj3%ityM|dN-o{$5Fl-g#k(8P z`YC7-z^NbJuuS*|g}t@qZ1sqcKG2vV${X11zQh;PZmS9+@({uRNs0SD87_O4qm9^X z|A61CO3AhAR0EldTL(HH7rtP;92eobChAcxLe?4#rHo%8k@?WK+=!?6Ck2{nCF{)3 zV-0ilGi$O{5aF*iFoDf`L3BTcXma%H0g)fk`q)=XTFML0-OWaZE|=! zInQ?I;zeeH)%e=ATua>cjj-&5FM0Tt4KJ@|(}uH&u8dh^&$A%+Z(@F2k2@%B`1|Cl ziq<+a={i$MIiz=MXou;({62Hvtt%DzrP z`0Y|g&3Pb9`ZerL>1^@-rFr7f6a9ZVsWDxVuFHF*(a6otIU_iCqa6mI2hW8N)MS^wFQZGI@3o+Xsmo3x+GjG0UA}i)Vq)<5Dvxq(@yqGV@Ug-~%5HZP)Ndi5QX?UjbJCq_*7HRxQ*tn9^d z>F3Ap(Iu3BvfkbgH%?t8tgt112OS5Adr*zE9FeDHCjFQVQdt3j^+nP;o3Zg@9#2rsirs0p2ewyOjLu|W>fSh?p2(
W(=MhC5^qxy;G8WbckA9_fDfZhZW(IfoF2ZSA3XPuRn38Y*m! zix|Z19*&j*_EmAeHiqot>xHFQPHV0+flC>U-5l%!*pjIeLz1Z1Vfg#~oC7qLh68C% zJil)W1ZcD~r+KZY>Jb9;oPR~Ob6E;)ZbzErk5ZZy)LPk!u&d$!2)|UeotqcsP8c(3 z++E*6r$pwdzo3~+1o+}Bobw7eGXjRf`xIOs&a3r2SM#X3ul%&M^2m%oh><#|lZY0u z7rgA6n7GK>{?d)NPb+nR)d@P zfcP2~Mhz*FG}&tZ1mJ4t<{nb&yWxe>-!X@?hZ#i|9cj^oXOEyMqIY?Vmy7itO3Mum zKNn=TPxt)e$~ejQ>{B%F4&BjiW3$jku%xxC?~e|UF0r}eCPMbC`<-^RegK#WMyZwx zkF8(mU2dJ?$;YKt7utLTou(d@HHpx1h;YiE&i0kj_hqcX`$~G&@Zbtd!<+L@^peU% zcsVT*%M-n?w5d$a>U@NE-*La1jEsNw7=@uWCQwbC-{`54fbEQ`eWP+hq=m+S+X;h_ zDW%sC*Q=z1I-lRD&&-|I<^i=cY|?gLRoKhswCDR>3u?8Bq}`>mD+*s0*xT*;y6CzP zx}Ft4dB*rDfm@{#;(#?D?>QD0%qfzH@k8<_I@dlA9Xkm~=o64MP=6WyzW8c`BSUx! z!CeHKT?G`5If&VrYW0nxhm1|}-GdhE$(PGMDX%EZk2xl6k|bQUQW6j+n@*=eIp6g5 zmLBs-x4$si4J9V-4wbh~k#;^I)n`};j(@_vYqj9P&ncex|dGplAtwEdm_JbILy z<2|mmYz6Z@xG&Kjz1YcKlm~|z6n_~7Pnm*KCbY3m)#|q0-XxXW!_`g&5mv(KPxCtP#i_(Z$h1)^EByV-7pB0jpK)?%5o36Me%7N|!`8Gt zkL#`Bb9+at72yR-`w{I{(|1Zg8sMiJ1RnfB`|MZRjdGABW&D22LJwH~Q(%=F?Rfi2 zX{7Jg{@-y0nd!}c6G(jeKyktMqVkR87iH4_Fb?^5yVsRfS3kWtAX(Bh?>=Fg>Gs?d z+6Po=NBcoTXSS3&gxSLkTRwoJCo6Rx$)LQb_`ltSe??NF;xh>^4Q!`;&Bvj~#yLa+ z60^_H@Uvw0IP9X?57fK6mjtAN#HC7m$JaN58lZq>m7a*Z2`TYGJbE!IG;wCGJ#NU@ z7VgVrrCzul$m_+1y|Mkr>$_5LhC|1?O8arBV;vY!_YP4Rm@5#s;R7G9v@dDEL773d zy5DDiGEOJ)79V%;KgR_vx0~FPf-n|O3{SO{h|4jX7Mr;GjwI%raxarY1n@=J70HnM zjhz~4xDxKsv`W|I)K6uWes79vdo8_rZtSgB81Kg;wX6%nazy(~EB&&OKQgQ=iyMT< zPmiMP*2w$IrX^%x`!s7W! z5y;-`^gjW06d*dJqGMDfz2HahHx4(aQ0XjUN0>+O0b?maN9AQQ%+OwN2BME8b5bRy z@Pglle!eug$I==*KM8~yM6MLtGjq<(O7!Il00+aHQ&ta;hy5BhKMHH>Gj!W8k^VAY zv+GYdWf2Qp-3yc}+6YyyBhpKJh)-a?TU3nmjHx)We^2ViRn&wRAgmn*9;5CZ1ypSm zLLu%)3w=wLe!Y_7)+bI1vST*LN-rfph1Sn(C!f=o!fQSc#3R7RMc(_{HG!BkjyV~N zM!<6GwVdqa`6suYmXjM8(9OTdJ^hypNR>5;CY-#JuQf^{*24M398>(p6PIjDb8M$K z;XgOq7&Rsn@ALWsB&qi-otrjEwvvOn#|0t#N>5r%ralob|Lx6LMJ6czX zGckTW`X;8|?2tK&$7T1cPV^F)?-GM_;TB%O1*`3QhaM^?j_i~h46S{fR{b1lBNW?fS8&_Pmg9Lib}862faU$7x^u|C~?fJ zEd~rt`9eRht7Ogp?z82K-`O37j<3z`=yR6l@d->}7w!%l7cOkKDL00ES=(jVqTe-w zDtE$XUPWbmK~qdJ9bdByt;{{MKC@6%RZ|KWS}=6GzPVWpF#)Ud*z>*$j#bnFCrRcQ zogvk|Eim8m|0o}~PxNyds1)Q_HJSMok&)H)z5+9a%)ecA6=z-gbXwxa&-~&LU+_Sp z2%6%s63CrT1L9iKP#!5&{u4!(%5tI!(cgeg2@@I`Z|<{z@6REGFdpE zzF%CS=$tA*(>mLAmpEc}RF{f>JbZUJ+Zk@{`Sv|ju_XBXaD~|3Q=2wrMP2s27q<$b zLUF_J?DvTbb=Im>t*l+aRi{z(4g1gS=>u;haHsZBSJVNQtR5dt;k+fQ=v|e z$8uA0qn8tT?~&vSp^=;|O{rXFiX6aBM*~FgG*N8mQrQuE8N(Iojau_=Yhe>l%r^b` zJT2kmE?+zPeZSWF{xR5eqqpmaZ<3ANPv%yV*mj#X+#NOSJ30Q9mGS!#w`jR_$z1{{;fP9W4;-8ozx0Q9 zOG1fz%xT&Uwn#~Yi!CPizuKFdKNKXG6xm{LtQF9-_=>sdM%@UVW4wk?W>2x{fv|qp zrwY}Lai^EKL<6f)x_lZQ7>Eb;eXzV{g}?+C+pvwM%uez->}n~2kol);k%M#3BIeq+ z@x7!Ifbp*On!ZFKtO}&|bkl+QX4n+i>&MbcZ?f^sE8^sS`ewEP-Vo*X^vRItR@28AfKT(nVc^i#0g7BABzCRnTdoz)lHs(9xtRq< zYTtsaG4#>u;$wy}zg{r$A-UIa)WiYEKDhX@e~+<=;V)4dVlDXLV3qO`Jf&#fNu zJ_su)1)z6dTiN6ccy>w|F+vNb&YJv;QbM3AKxZ(#zI9dL zxn@=HRJi#lGBMFucM6!CZcI_TvpUWSW)0d;{%Y6FgRtArQ95RcsLF5e>)XPojz@lm zt0axxxHIgR{eJOW8Lx>?)VbDIX9?7!8*o08-(`W(5FyauK&-5`t|(}18cfey#7Pse zV7ek#1`b*JEemOH&O)57(krF0JLKE5)-VfC*4~In%de2sI)ASxln`@hHz3n%wvu=4 zimku98GzDu3N->J|8HJeEs#vKUy?i(E@bur{ZCkX(MVo8o4Os^b785;1n&JGI{M#X z;J+B`zp>f>-R`DFCMNWM!&)~X_IRH_b={dTh2{fQUT@6Fa~iqUd{gS@>L||b2HA+X zUEhV0{$B&xe{%-GL<(s*>g{wTd#9XVD?OR-s|LV?b{&CeKW^$%UcD45b>u%(A3_o{ zEjONq)mp-AHg+ksAkRYCP#Z)sIG_0}Rv92YH0R@*?G71jwgI1adFE;sW_LJdU2D9? zRZHHeT&`5Kap3;}e)b=%u91jbqeB!~77ec6oA`m7$EgliF|)GTp6Sazr!Egn?~wUE zeV@Ix5M=yzpm!Joall|@rAO~}+aK*7j_u5%-4(J!(%sSBuTwVUMEN-+zckY#}oR2u(8B6?1#F(x-LT7XSUFI2<>I z#1wc6q(Kiz+#FK_SEbd|#u859 zh3d894@(gkuR?kJ#0(48ZC`^I*`^^aoqgSPnvC7hO!wK~CfTE1THM!<}Mr>TRJ zQf6OkKD_0drk;wU3ilqED3Fq9DEcF^oOWaN7x!6mVRhYuQCp49eG3VP<6L2Y;3`e_ zQOEWjeQfl`&c?Z2W*km|`^NfgJ_^dhX!R7d`tW-)G>1yT^%c+Td_mZIHXO zg->qN&2XAmImx)QS@UI=DRVFX%?Ce-m*z_Nz_;Yia-IH`7V()B~dOaO?rO5&9 zun)az9%)p8%X{JxhP0-Jz1c8#dn_nepUYnI?k$052_fS(V>}X*nzYH(_kQ7iI7!7K z6|j$T*}}eDw37pdk5B!^bY0nPF#0=vLaUZy zyucjbr55}f0>V1;_>tZ7SzZQ5k}Sb#BkQ;=A!8#%&>j5r7VcN2WzfJ|Bxx<}WuSIU zkYm(+;QkL5)LZrbCSbdFG_C00QHNV18_@4hYIsK7x8l3yPLB{jkiO|oo$R7CRu0E! z@8R#a)u%^?%o*lh9P=0Y62I6Wy8NyK?t~k*1`@GR-#P%NW`YRI6=#5W- zdy_vch3_GJ2CL-vXR_@)Pm|>Mf+;Az%duYRZms8NMGx-~L3A^F98 zqed!M30DG8l|PtHEQowLNN+2i0zLf+2kyGfNj?>CSnzm6Y4G>4*oXpKjH%Hf+h-@? ziJIs+1}k-)aOp`g#>u22O!&%paJPJsis=c#7bI7rLEq{FlTu(`_d(ljkO9!1UYjB^3=IkE}=6E zcETTV1LRDt6|IUwT>N5BJZ!QSNh9yY-&-B5kJ8HEW~Q)QHL6Yv>!Z9ov)QrkAFE za7Z((BLUH|>z=l9F|;j%>)WV1UMnYZiVfrV69>))gzPjYrO3P+sHWBLnUB`qPhU?X zkb9*q>v~V2Ze)lXqSj&3B5!-{gGizwU(@{m9T@!&xe8|C?5uOq5}9a&LH)@d=+eXAv!&-2?mwm63mNher~l6g zmBc%f@bR>1lpu^*xp=FPZIZJvZtm9l6N9MA_SY-2EST;itl1s~UD7!M`|ZqLUYk%I z_pA2ZM4bLWp6yDF+Vt&YXHBsS8-S1rsUa)rQv2%y(6nA4i-vs$+U`spDz8NIgfaDr zjG*C;rm^v_On^N-K$Ht0-Up^Fo~T&&)byP02l5WzJXm`P>^w{v&>Ao&hqdE)WfT&z zLiZ848sre5Xg=}az|}Q6lXu;Db)52XY&Kcne=_3$XnkrrP^SRN zp5&M!Yg1bJIB~qIQQNiE(+>Q{}}j5Sj36+~t>! zVUPgQ|&6OdZ=@F>d=CJ-*tNj^0T{DdTk(NBzO3yj*$htoB`%i8) zAt`kpUN|$Gz7CyU`5i@T+de_^%v0@xl4k-s`ZA-k)A%ob);br`O!bnfgXK)iOmbn? z`)n)K9e3)z*N=G3Scm_#?!Pcg5q&U&3mty)!UgDTnz5}~C~_-Ld_x16DJOOR7N?*{ zZXBFsaXgOb)mCV-(COX9AxrpUe3p8!q7VqWQKce3og35FgSpmFr_B*)+@r)KS+Ukl z4Nf0i)oM5bSp`eGAMQOzKM`f%5|R?==2NPw)VS%sA7nC`&;PUTuHYk#zfRe@E|>k| ziXy(it`!V#*ndSRxbdqD+bfJBn&i?nQLE0pQM=aBYC`6&Q4=;^qfl}t8A0Sz>3ood z`4-}FYxR)rYZtF~oVN{4RU-9z8aumDy?n|Xbln8yDnmSXo^{$62WrDs5$8i^^tXpY zHsqI06{(C^A!RD3`Q$}siC?}|E-|T;=VidQllb8#@$4Ff?Ow73psVxaihHlP4B|W? z6;-h&l#~U`=NV*CG4F9z{;fqWRIv?!x1~k=N-55ISj&n4Uo&iq=`=X3)Su6Jdwp~k zCaCR^7yjk#-Lsp<#nUOOw}P--cc{?a)+MB$@<8ZcJs8tB4NKa(+>lIEtNIcM*|B3GPE{S2e zy?H-MBn>J{YC2NX*z2KSn+PH}wb9TO2r@b*OC9!f%o}>3u`WKNUyABSAHMVK8uR<6 z;QU4Pk@rrzC70>&*a=%1Zyy)sn*p8jq>>1~LuYVYpL+#7YE8cQUygUM1c)bL3? zso)tXGmwH1YEj@l5}I}C$pBvme)##8~j`ZbBfTC7*0xP5toV; zD_h-tp-lhh?OO{zNz;EjwD)Bkb7a9lW8nT|$KRyj2jhLzy4P3h- z&D~;LN7XHDDD&FmzWv!qztR3)=KieIALA0HU9s1~m_IQUaIcLeF|mds%@YTWa9`Rz z^J|WWB^X7|hrFp3pE4J{wkL?pO^HY+Yd|_Sy5%c>J!p+6B*X^oKE;oHszj!;-*(C1 zsg<^UCsbM~W>ht_8?b8j>MK)#qL6ucSwGm-zGM^rKK(JK9Az-@w)-hd=trcKLRjP1 zbCM=}ebz;#j*g^ z&_F4JPbxr9SouQ2Y4D2w$Zk!iLi!a)`A-Wyn|MaCBZE}QbPXaTB8$<=i(V_vGiZqr z5vT~TM)O!P5Im@6uF9W3GS3NnLmtnT4bpmC{mxa_of~x^a)nG!12zjWN#R^ZnCH{A zr!JS-HeI5njl2-er&B9{N5tAp8M?qH=7nY9(7Ab^hd(tNXd9BFzC&TtX-urVhET*n&*c)h(CUqnN^lAJ}Su@#XGikEM+@oYvi`xCIBr7 z#oxN4+kzAA8+U$I3)Itl*)bjh$cW%}S;kgLp(Jk+B$!7N^-V_c8le5)TXt7Aj&Pou zrlzJRi<4;diz)EFjlrenuNtapJ2aYn{F-QFp+|A^&&=&|DlRrhz39x$`AL~7PRiqO zQ6xi=bdUwB(yf{7$6j^V*z~}$Dv!Xf@1KjJj<$BWAb(l%uz+>-P5;G-paLA*->$5D!Q%I{39~H8#Q2qy+|J<%^ID9wUJD4&1MXjw(pyXNS?b9YltftbR6;SOp-t)K29lCqc!xL+1iJ&-> z{Nr$798+oO-|1T|T*9P(F2+q?(AWY%aquw!RKkFI6|ijGr+a;A1gzp!yxraJ6m71+hSzc+&7e^Mv?jqnV05|x4H+H z_&wc-)77lcfh-=1ZJrnHY{sPa-AHBrDSdQ2A5OVD40IQGe1C&vOIi|}k3R*-GhS5s zkW2h}IrUMkNg3^RYiN7Vbw1k{8sFIAE;+Q{DJ68_^`G8VaU^uQRw+E2z8I;-0UzN0 zOg^PozK|<=`N;7qa;9sh20GE9b0%q)nhLJL%UG8)?A_#Les!(d}#zd5MrRB0cHEH?4vH@(dr;Qr5q{AcfL=k|2;A8[0]): Promise => createWidget(page, 'dxPivotGrid', { + dataSource: { + fields: [ + { dataField: 'region', area: 'row' }, + { dataField: 'city', area: 'column' }, + { dataField: 'amount', area: 'data', summaryType: 'sum' }, + ], + store: sales, + }, +}); + +test('PivotGrid renders its areas and answers its options', THEMES, async ({ page }) => { + await createPivotGrid(page); + + const pivotGrid = new PivotGrid(page, '#container'); + + await expect(pivotGrid.dataArea).toBeVisible(); + expect(await pivotGrid.option('rowHeaderLayout')).toBe('standard'); + + await pivotGrid.option('rowHeaderLayout', 'tree'); + + expect(await pivotGrid.option('rowHeaderLayout')).toBe('tree'); +}); + +test('PivotGrid markup', async ({ page }) => { + await createPivotGrid(page); + + await testScreenshot(page, 'PivotGrid markup.png', { element: '#container' }); +}); diff --git a/e2e/playwright/tests/editors/checkBox/common.spec.ts b/e2e/playwright/tests/editors/checkBox/common.spec.ts deleted file mode 100644 index e49f1ec699ab..000000000000 --- a/e2e/playwright/tests/editors/checkBox/common.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { expect, test } from '../../../fixtures'; -import { createWidget } from '../../../helpers/createWidget'; -import CheckBox from '../../../models/checkBox'; - -// The tags list the themes this test also runs in; a test without them runs in the default one. -const THEMES = { tag: ['@generic.light', '@material.blue.light', '@material.blue.light.compact'] }; - -test('CheckBox switches its state on click', THEMES, async ({ page }) => { - await createWidget(page, 'dxCheckBox', { value: false }); - - const checkBox = new CheckBox(page, '#container'); - - expect(await checkBox.isChecked()).toBe(false); - - await checkBox.element.click(); - - expect(await checkBox.isChecked()).toBe(true); - expect(await checkBox.option('value')).toBe(true); -}); - -test('CheckBox renders the indeterminate state', THEMES, async ({ page }) => { - await createWidget(page, 'dxCheckBox', { value: null }); - - const checkBox = new CheckBox(page, '#container'); - - expect(await checkBox.isIndeterminate()).toBe(true); -}); diff --git a/e2e/playwright/tests/navigation/button/common.spec.ts b/e2e/playwright/tests/navigation/button/common.spec.ts deleted file mode 100644 index e192ae28a3d0..000000000000 --- a/e2e/playwright/tests/navigation/button/common.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { ButtonType } from 'devextreme/common'; -import { expect, test } from '../../../fixtures'; -import { createWidget } from '../../../helpers/createWidget'; -import { - addCaptionTo, - appendElementTo, - setAttribute, - setClassAttribute, -} from '../../../helpers/domUtils'; -import { testScreenshot } from '../../../helpers/screenshots'; -import Button from '../../../models/button'; - -// The tags list the themes this test also runs in; a test without them runs in the default one. -const THEMES = { tag: ['@generic.light', '@material.blue.light', '@material.blue.light.compact'] }; - -const STATES = ['default', 'focused', 'hover', 'active', 'selected', 'disabled']; - -const BUTTONS = [ - ...['danger', 'default', 'normal', 'success'].map((type: ButtonType) => ({ - type, - text: `${type[0].toUpperCase()}${type.slice(1)}`, - })), - { icon: 'find', text: 'Find' }, - { icon: 'find' }, -]; - -['text', 'outlined', 'contained'].forEach((stylingMode) => { - const testName = `Buttons, stylingMode=${stylingMode}`; - - test(testName, async ({ page }) => { - await setAttribute(page, '#container', 'class', 'dx-theme-generic-typography'); - await setAttribute(page, '#container', 'style', 'width: fit-content; padding: 8px;'); - - for (const state of STATES) { - await appendElementTo(page, '#container', 'div', `mode${state}`); - await setAttribute(page, `#mode${state}`, 'style', 'display: flex; gap: 8px; margin-bottom: 16px;'); - await addCaptionTo(page, `#mode${state}`, state); - - for (const [index] of BUTTONS.entries()) { - await appendElementTo(page, `#mode${state}`, 'div', `button-${state}-${index}`); - } - - for (const [index, config] of BUTTONS.entries()) { - await createWidget(page, 'dxButton', { - ...config, - stylingMode, - disabled: state === 'disabled', - }, `#button-${state}-${index}`); - } - - if (state !== 'default' && state !== 'disabled') { - for (const [index] of BUTTONS.entries()) { - await setClassAttribute(page, `#button-${state}-${index}`, `dx-state-${state}`); - } - } - } - - await testScreenshot(page, `${testName}.png`, { element: '#container' }); - }); -}); - -test('Button reports its text, selected and disabled state', THEMES, async ({ page }) => { - await createWidget(page, 'dxButton', { text: 'Find', icon: 'find' }); - - const button = new Button(page, '#container'); - - await expect(button.text).toHaveText('Find'); - expect(await button.isSelected()).toBe(false); - expect(await button.isDisabled()).toBe(false); - - await button.option('disabled', true); - - expect(await button.isDisabled()).toBe(true); -}); diff --git a/e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=contained (fluent.blue.light).png b/e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=contained (fluent.blue.light).png deleted file mode 100644 index ce153a4cb49496b3c485784f0ae60312c72cd297..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36994 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 b#0LWARw_uY<#sJQ8-R_agGHs8_lvKze diff --git a/e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=outlined (fluent.blue.light).png b/e2e/playwright/tests/navigation/button/etalons/Buttons, stylingMode=outlined (fluent.blue.light).png deleted file mode 100644 index 2ee920bbf97b063fa99e5412e3e25b8eac21d229..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37094 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 v|99i|zMw%szP@N52 Date: Thu, 27 Aug 2026 23:06:12 -0300 Subject: [PATCH 18/22] Keep the Button and CheckBox models: the composite models build on them --- e2e/playwright/models/button.ts | 24 ++++++++++++++++++++++++ e2e/playwright/models/checkBox.ts | 23 +++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 e2e/playwright/models/button.ts create mode 100644 e2e/playwright/models/checkBox.ts diff --git a/e2e/playwright/models/button.ts b/e2e/playwright/models/button.ts new file mode 100644 index 000000000000..81df6f59b1d2 --- /dev/null +++ b/e2e/playwright/models/button.ts @@ -0,0 +1,24 @@ +import type { Locator } from '@playwright/test'; +import type { WidgetName } from './types'; +import Widget from './internal/widget'; + +export const CLASS = { + text: '.dx-button-text', + selected: 'dx-state-selected', + itemSelected: 'dx-item-selected', +}; + +export default class Button extends Widget { + public static className = 'dx-button'; + + public get text(): Locator { + return this.element.locator(CLASS.text); + } + + public isSelected(): Promise { + return this.hasClass(CLASS.selected); + } + + // eslint-disable-next-line class-methods-use-this + public getName(): WidgetName { return 'dxButton'; } +} diff --git a/e2e/playwright/models/checkBox.ts b/e2e/playwright/models/checkBox.ts new file mode 100644 index 000000000000..0ba7c189b705 --- /dev/null +++ b/e2e/playwright/models/checkBox.ts @@ -0,0 +1,23 @@ +import type { WidgetName } from './types'; +import Widget from './internal/widget'; + +export const CLASS = { + checked: 'dx-checkbox-checked', + indeterminate: 'dx-checkbox-indeterminate', + icon: '.dx-checkbox-icon', +}; + +export default class CheckBox extends Widget { + public static className = 'dx-checkbox'; + + public isChecked(): Promise { + return this.hasClass(CLASS.checked); + } + + public isIndeterminate(): Promise { + return this.hasClass(CLASS.indeterminate); + } + + // eslint-disable-next-line class-methods-use-this + public getName(): WidgetName { return 'dxCheckBox'; } +} From b5e119e31cf8736d96cba691c22518bc90139814 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Mon, 7 Sep 2026 11:19:51 -0300 Subject: [PATCH 19/22] Migrate a small PivotGrid slice so the package runs real tests Claude-Session: https://claude.ai/code/session_01SLZQZ92qqekVtCZBc87Ajg --- e2e/playwright/README.md | 4 +- e2e/playwright/helpers/createWidget.ts | 66 ++++++++++- e2e/playwright/helpers/screenshots.ts | 84 +++++++++++-- e2e/playwright/models/internal/hasClass.ts | 7 ++ e2e/playwright/models/internal/widget.ts | 7 +- e2e/playwright/models/overlay.ts | 46 ++++++++ e2e/playwright/models/pivotGrid.ts | 26 ---- .../models/pivotGrid/columnHeaderArea.ts | 37 ++++++ .../models/pivotGrid/columnsArea.ts | 22 ++++ .../models/pivotGrid/dataHeaderArea.ts | 19 +++ .../models/pivotGrid/fieldChooser.ts | 45 +++++++ .../models/pivotGrid/filterHeaderArea.ts | 23 ++++ .../models/pivotGrid/headerFilter.ts | 17 +++ e2e/playwright/models/pivotGrid/index.ts | 106 +++++++++++++++++ e2e/playwright/models/pivotGrid/overlay.ts | 18 +++ .../models/pivotGrid/rowHeaderArea.ts | 23 ++++ e2e/playwright/models/pivotGrid/rowsArea.ts | 22 ++++ e2e/playwright/models/popup.ts | 47 ++++++++ e2e/playwright/models/treeView/index.ts | 45 +++++++ .../models/treeView/treeViewNode.ts | 32 +++++ .../common/pivotGrid/contextMenu.spec.ts | 93 +++++++++++++++ ... contextmenu width (fluent.blue.light).png | Bin 0 -> 44465 bytes ...Grid contextmenu width (generic.light).png | Bin 0 -> 30592 bytes ...g_enabled_T1210807 (fluent.blue.light).png | Bin 0 -> 26023 bytes ...lling_enabled_T1210807 (generic.light).png | Bin 0 -> 31054 bytes ...enabled_T1210807 (material.blue.light).png | Bin 0 -> 23784 bytes .../export/onExportingOption.spec.ts | 31 +++++ .../tests/common/pivotGrid/reference.spec.ts | 2 +- ...mmary_display_mode (fluent.blue.light).png | Bin 0 -> 13563 bytes ...mmary_display_mode (fluent.blue.light).png | Bin 0 -> 12982 bytes ...ortWithSummaryDisplayMode_T1173442.spec.ts | 111 ++++++++++++++++++ .../virtualScrolling_T1210807.spec.ts | 92 +++++++++++++++ 32 files changed, 983 insertions(+), 42 deletions(-) create mode 100644 e2e/playwright/models/internal/hasClass.ts create mode 100644 e2e/playwright/models/overlay.ts delete mode 100644 e2e/playwright/models/pivotGrid.ts create mode 100644 e2e/playwright/models/pivotGrid/columnHeaderArea.ts create mode 100644 e2e/playwright/models/pivotGrid/columnsArea.ts create mode 100644 e2e/playwright/models/pivotGrid/dataHeaderArea.ts create mode 100644 e2e/playwright/models/pivotGrid/fieldChooser.ts create mode 100644 e2e/playwright/models/pivotGrid/filterHeaderArea.ts create mode 100644 e2e/playwright/models/pivotGrid/headerFilter.ts create mode 100644 e2e/playwright/models/pivotGrid/index.ts create mode 100644 e2e/playwright/models/pivotGrid/overlay.ts create mode 100644 e2e/playwright/models/pivotGrid/rowHeaderArea.ts create mode 100644 e2e/playwright/models/pivotGrid/rowsArea.ts create mode 100644 e2e/playwright/models/popup.ts create mode 100644 e2e/playwright/models/treeView/index.ts create mode 100644 e2e/playwright/models/treeView/treeViewNode.ts create mode 100644 e2e/playwright/tests/common/pivotGrid/contextMenu.spec.ts create mode 100644 e2e/playwright/tests/common/pivotGrid/etalons/PivotGrid contextmenu width (fluent.blue.light).png create mode 100644 e2e/playwright/tests/common/pivotGrid/etalons/PivotGrid contextmenu width (generic.light).png create mode 100644 e2e/playwright/tests/common/pivotGrid/etalons/rows_do_not_overlap_data_fields_if_virtual_scrolling_enabled_T1210807 (fluent.blue.light).png create mode 100644 e2e/playwright/tests/common/pivotGrid/etalons/rows_do_not_overlap_data_fields_if_virtual_scrolling_enabled_T1210807 (generic.light).png create mode 100644 e2e/playwright/tests/common/pivotGrid/etalons/rows_do_not_overlap_data_fields_if_virtual_scrolling_enabled_T1210807 (material.blue.light).png create mode 100644 e2e/playwright/tests/common/pivotGrid/export/onExportingOption.spec.ts create mode 100644 e2e/playwright/tests/common/pivotGrid/sort/etalons/T1173442_after_sort_with_summary_display_mode (fluent.blue.light).png create mode 100644 e2e/playwright/tests/common/pivotGrid/sort/etalons/T1173442_before_sort_with_summary_display_mode (fluent.blue.light).png create mode 100644 e2e/playwright/tests/common/pivotGrid/sort/sortWithSummaryDisplayMode_T1173442.spec.ts create mode 100644 e2e/playwright/tests/common/pivotGrid/virtualScrolling_T1210807.spec.ts diff --git a/e2e/playwright/README.md b/e2e/playwright/README.md index 65401290f81b..7705a9a75a2f 100644 --- a/e2e/playwright/README.md +++ b/e2e/playwright/README.md @@ -91,12 +91,12 @@ import { createWidget } from '../../../helpers/createWidget'; import { testScreenshot } from '../../../helpers/screenshots'; import PivotGrid from '../../../models/pivotGrid'; -test('PivotGrid renders its data area', async ({ page }) => { +test('PivotGrid renders', async ({ page }) => { await createWidget(page, 'dxPivotGrid', { dataSource: { store: [] } }); const pivotGrid = new PivotGrid(page, '#container'); - await expect(pivotGrid.dataArea).toBeVisible(); + await expect(pivotGrid.element).toBeVisible(); await testScreenshot(page, 'PivotGrid markup.png', { element: '#container' }); }); ``` diff --git a/e2e/playwright/helpers/createWidget.ts b/e2e/playwright/helpers/createWidget.ts index 4559a84ff72a..8eec5c074055 100644 --- a/e2e/playwright/helpers/createWidget.ts +++ b/e2e/playwright/helpers/createWidget.ts @@ -10,6 +10,30 @@ const DEFAULT_OPTIONS: CreateWidgetOptions = { disableFxAnimation: true, }; +const FUNCTION_MARK = '__dxFunction__:'; +const DATE_MARK = '__dxDate__:'; + +// Playwright passes the arguments of "page.evaluate" through JSON, which drops the handlers and +// flattens the dates a widget configuration is full of. The TestCafe runner kept both, so they are +// marked here and revived in the page — otherwise every "onInitialized" would arrive as undefined. +const serializeOptions = (options: unknown): string => JSON.stringify( + options, + // eslint-disable-next-line func-names + function (this: Record, key: string, value: unknown) { + const raw = this[key]; + + if (typeof raw === 'function') { + return `${FUNCTION_MARK}${raw.toString()}`; + } + + if (raw instanceof Date) { + return `${DATE_MARK}${raw.toISOString()}`; + } + + return value; + }, +); + export const createWidget = async ( page: Page, widgetName: TWidgetName, @@ -20,12 +44,50 @@ export const createWidget = async ( ({ name, options, elementSelector, disableAnimation, }) => { + const functionMark = '__dxFunction__:'; + const dateMark = '__dxDate__:'; + + const compile = (source: string): unknown => { + try { + // eslint-disable-next-line @typescript-eslint/no-implied-eval + return new Function(`return (${source});`)(); + } catch { + // A method shorthand — "onInitialized(e) { … }" — is not an expression on its own, so it + // is compiled as the single member of an object literal. + // eslint-disable-next-line @typescript-eslint/no-implied-eval + const holder = new Function(`return ({ ${source} });`)() as Record; + + return holder[Object.keys(holder)[0]]; + } + }; + + const revived = JSON.parse(options, (_, value: unknown) => { + if (typeof value !== 'string') { + return value; + } + + if (value.startsWith(functionMark)) { + return compile(value.slice(functionMark.length)); + } + + if (value.startsWith(dateMark)) { + return new Date(value.slice(dateMark.length)); + } + + return value; + }); + (window as any).DevExpress.fx.off = disableAnimation; - (window as any).widget = ($(elementSelector) as any)[name](options)[name]('instance'); + + // A configuration that has to reach for something on the page — a store built by + // "DevExpress.data" — arrives as a factory and is only called here, as it was in TestCafe. + const resolved = typeof revived === 'function' ? revived() : revived; + + (window as any).widget = ($(elementSelector) as any)[name](resolved)[name]('instance'); }, { name: widgetName, - options: widgetOptions, + options: serializeOptions(widgetOptions), elementSelector: selector, disableAnimation: disableFxAnimation, }, diff --git a/e2e/playwright/helpers/screenshots.ts b/e2e/playwright/helpers/screenshots.ts index cf8a2aa3a19b..6104c175527b 100644 --- a/e2e/playwright/helpers/screenshots.ts +++ b/e2e/playwright/helpers/screenshots.ts @@ -21,16 +21,85 @@ const getScreenshotName = (baseName: string, theme?: string): string => { : `${baseName}${themePostfix}.png`; }; -// No element means the whole viewport, the way the TestCafe comparer read a missing element. -const resolveTarget = ( +const resolveLocator = ( page: Page, element: Locator | string | null | undefined, -): Locator | Page => { +): Locator | null => { if (typeof element === 'string') { return page.locator(element); } - return element ?? page; + return element ?? null; +}; + +// The page screenshot is clipped to the layout viewport: Playwright would otherwise include the +// scrollbar, which TestCafe left out, and every full-page etalon would have to be re-recorded. +const viewportClip = async (page: Page): Promise<{ + x: number; y: number; width: number; height: number; +}> => page.evaluate(() => ({ + x: 0, + y: 0, + width: document.documentElement.clientWidth, + height: document.documentElement.clientHeight, +})); + +// TestCafe shot an element that outgrows the viewport by scrolling its start into view and taking +// the part of it that was on screen; the Playwright element screenshot would instead stitch the +// whole element, which no existing etalon matches. Only such an element takes this path — one that +// fits is shot as it always was. +const clippedToViewport = async ( + page: Page, + locator: Locator, +): Promise<{ x: number; y: number; width: number; height: number } | null> => { + const box = await locator.boundingBox(); + + if (!box) { + return null; + } + + const viewport = await viewportClip(page); + + if (box.width <= viewport.width && box.height <= viewport.height) { + return null; + } + + // The clip has to stay inside the viewport: a full-page capture widens the viewport to the whole + // document, the scrollbar goes away, and a widget that fills its container re-lays out to the + // wider space — the shot would show a layout the page never had. + await locator.evaluate((element) => { + element.scrollIntoView({ block: 'start', inline: 'start' }); + }); + + const scrolled = await locator.boundingBox() ?? box; + const x = Math.max(scrolled.x, 0); + const y = Math.max(scrolled.y, 0); + + return { + x, + y, + width: Math.min(scrolled.width, viewport.width - x), + height: Math.min(scrolled.height, viewport.height - y), + }; +}; + +const expectScreenshot = async ( + page: Page, + locator: Locator | null, + name: string, +): Promise => { + if (!locator) { + await expect(page).toHaveScreenshot([name], { clip: await viewportClip(page) }); + return; + } + + const clip = await clippedToViewport(page, locator); + + if (clip) { + await expect(page).toHaveScreenshot([name], { clip }); + return; + } + + await expect(locator).toHaveScreenshot([name]); }; export async function testScreenshot( @@ -51,9 +120,10 @@ export async function testScreenshot( await themeChanged?.(); } - const target = resolveTarget(page, element); + // No element means the whole viewport, the way the TestCafe comparer read a missing element. + const target = resolveLocator(page, element); - await expect(target).toHaveScreenshot([getScreenshotName(screenshotName, theme)]); + await expectScreenshot(page, target, getScreenshotName(screenshotName, theme)); if (shouldTestInCompact) { // The theme of a "- compact" job already ends with the suffix; appending it twice would ask @@ -63,7 +133,7 @@ export async function testScreenshot( await changeTheme(page, compactTheme); await compactCallBack?.(); - await expect(target).toHaveScreenshot([getScreenshotName(screenshotName, compactTheme)]); + await expectScreenshot(page, target, getScreenshotName(screenshotName, compactTheme)); } if (theme || shouldTestInCompact) { diff --git a/e2e/playwright/models/internal/hasClass.ts b/e2e/playwright/models/internal/hasClass.ts new file mode 100644 index 000000000000..36b94b970c02 --- /dev/null +++ b/e2e/playwright/models/internal/hasClass.ts @@ -0,0 +1,7 @@ +import type { Locator } from '@playwright/test'; + +export const hasClass = async (locator: Locator, className: string): Promise => { + const classes = await locator.getAttribute('class'); + + return classes?.split(' ').includes(className) ?? false; +}; diff --git a/e2e/playwright/models/internal/widget.ts b/e2e/playwright/models/internal/widget.ts index 9fe9f20177d8..3208f7041a93 100644 --- a/e2e/playwright/models/internal/widget.ts +++ b/e2e/playwright/models/internal/widget.ts @@ -1,5 +1,6 @@ import type { Locator, Page } from '@playwright/test'; import type { WidgetName } from '../types'; +import { hasClass } from './hasClass'; const CLASS = { focused: 'dx-state-focused', @@ -40,10 +41,8 @@ export default abstract class Widget { return this.hasClass(CLASS.disabled); } - public async hasClass(className: string): Promise { - const classes = await this.element.getAttribute('class'); - - return classes?.split(' ').includes(className) ?? false; + public hasClass(className: string): Promise { + return hasClass(this.element, className); } public async option(option: string | Record, ...value: unknown[]): Promise { diff --git a/e2e/playwright/models/overlay.ts b/e2e/playwright/models/overlay.ts new file mode 100644 index 000000000000..5ad6869fa9ef --- /dev/null +++ b/e2e/playwright/models/overlay.ts @@ -0,0 +1,46 @@ +import type { Locator, Page } from '@playwright/test'; +import type { WidgetName } from './types'; +import Widget from './internal/widget'; + +const CLASS = { + content: 'dx-overlay-content', + wrapper: 'dx-overlay-wrapper', +}; + +export default class Overlay extends Widget { + public readonly content: Locator; + + public readonly wrapper: Locator; + + constructor(page: Page, selector: Locator | string) { + super(page, selector); + + this.content = this.element.locator(`.${CLASS.content}`); + this.wrapper = this.element.locator(`.${CLASS.wrapper}`); + } + + // eslint-disable-next-line class-methods-use-this + public getName(): WidgetName { return 'dxOverlay'; } + + // The last wrapper on the page rather than the one under the widget element: when several + // popups are open the topmost one is the interesting one. + public getWrapper(): Locator { + return this.page.locator(`.${CLASS.wrapper}`).last(); + } + + public getContent(): Locator { + return this.page.locator(`.${CLASS.content}`); + } + + public isVisible(): Promise { + return this.option('visible'); + } + + public async show(): Promise { + await this.invoke('show'); + } + + public async hide(): Promise { + await this.invoke('hide'); + } +} diff --git a/e2e/playwright/models/pivotGrid.ts b/e2e/playwright/models/pivotGrid.ts deleted file mode 100644 index c32e74e9a93e..000000000000 --- a/e2e/playwright/models/pivotGrid.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { Locator } from '@playwright/test'; -import type { WidgetName } from './types'; -import Widget from './internal/widget'; - -export const CLASS = { - area: '.dx-pivotgrid-area', - dataArea: '.dx-pivotgrid-area-data', - fieldChooserButton: '.dx-pivotgrid-field-chooser-button', -}; - -// The reference model of the package: it shows what a squad writes on top of Widget — a Locator -// per part of the markup and the widget API through option()/invoke(). -export default class PivotGrid extends Widget { - public static className = 'dx-pivotgrid'; - - public get dataArea(): Locator { - return this.element.locator(CLASS.dataArea); - } - - public getAreaCell(area: string, index: number): Locator { - return this.element.locator(`${CLASS.area}-${area} td`).nth(index); - } - - // eslint-disable-next-line class-methods-use-this - public getName(): WidgetName { return 'dxPivotGrid'; } -} diff --git a/e2e/playwright/models/pivotGrid/columnHeaderArea.ts b/e2e/playwright/models/pivotGrid/columnHeaderArea.ts new file mode 100644 index 000000000000..e359822d04aa --- /dev/null +++ b/e2e/playwright/models/pivotGrid/columnHeaderArea.ts @@ -0,0 +1,37 @@ +import type { Locator, Page } from '@playwright/test'; +import HeaderFilter from './headerFilter'; + +const CLASSES = { + columnHeader: 'dx-column-header', + field: 'dx-area-field', + headerFilterMenu: 'dx-header-filter-menu', + scrollable: 'dx-scrollable-container', +}; + +export default class ColumnHeaderArea { + private readonly page: Page; + + public readonly element: Locator; + + constructor(page: Page, selector: Locator) { + this.page = page; + this.element = selector.locator(`.${CLASSES.columnHeader}`); + } + + public getField(idx = 0): Locator { + return this.element.locator(`.${CLASSES.field}`).nth(idx); + } + + // The header filter popup is attached to the viewport, not to the area. + public getHeaderFilterMenu(): Locator { + return this.page.locator(`.${CLASSES.headerFilterMenu}`); + } + + public getHeaderFilterIcon(idx = 0): HeaderFilter { + return new HeaderFilter(this.getField(idx)); + } + + public getHeaderFilterScrollable(): Locator { + return this.getHeaderFilterMenu().locator(`.${CLASSES.scrollable}`); + } +} diff --git a/e2e/playwright/models/pivotGrid/columnsArea.ts b/e2e/playwright/models/pivotGrid/columnsArea.ts new file mode 100644 index 000000000000..f6bb12034113 --- /dev/null +++ b/e2e/playwright/models/pivotGrid/columnsArea.ts @@ -0,0 +1,22 @@ +import type { Locator } from '@playwright/test'; + +const CLASSES = { + root: 'dx-pivotgrid-horizontal-headers', +}; + +export default class ColumnsArea { + public readonly element: Locator; + + constructor(selector: Locator, idx?: number) { + this.element = selector.locator(`thead.${CLASSES.root}`).nth(idx ?? 0); + } + + public getCell(rowIdx = 0, cellIdx = 0): Locator { + return this.element.locator('tr').nth(rowIdx).locator('td') + .nth(cellIdx); + } + + public getCells(): Locator { + return this.element.locator('td'); + } +} diff --git a/e2e/playwright/models/pivotGrid/dataHeaderArea.ts b/e2e/playwright/models/pivotGrid/dataHeaderArea.ts new file mode 100644 index 000000000000..b731a337c104 --- /dev/null +++ b/e2e/playwright/models/pivotGrid/dataHeaderArea.ts @@ -0,0 +1,19 @@ +import type { Locator } from '@playwright/test'; + +const CLASSES = { + dataHeader: 'dx-data-header', + field: 'dx-area-field', + box: 'dx-area-box', +}; + +export default class DataHeaderArea { + public readonly element: Locator; + + constructor(selector: Locator) { + this.element = selector.locator(`.${CLASSES.dataHeader}`); + } + + public getField(idx = 0): Locator { + return this.element.locator(`.${CLASSES.field}.${CLASSES.box}`).nth(idx); + } +} diff --git a/e2e/playwright/models/pivotGrid/fieldChooser.ts b/e2e/playwright/models/pivotGrid/fieldChooser.ts new file mode 100644 index 000000000000..33e1c4c7c1c1 --- /dev/null +++ b/e2e/playwright/models/pivotGrid/fieldChooser.ts @@ -0,0 +1,45 @@ +import type { Locator } from '@playwright/test'; +import type { WidgetName } from '../types'; +import Widget from '../internal/widget'; +import TreeView from '../treeView'; + +const CLASS = { + treeview: 'dx-treeview', + area: 'dx-area', + fields: 'dx-area-fields', + field: 'dx-area-field', + box: 'dx-area-box', +}; + +export default class FieldChooser extends Widget { + // eslint-disable-next-line class-methods-use-this + public getName(): WidgetName { return 'dxPivotGridFieldChooser'; } + + public getTreeView(): TreeView { + return new TreeView(this.page, this.element.locator(`.${CLASS.treeview}`)); + } + + public getAreas(): Locator { + return this.element.locator(`.${CLASS.area}`); + } + + public getRowAreaItem(idx = 0): Locator { + return this.getAreas().nth(1).locator(`.${CLASS.field}.${CLASS.box}`).nth(idx); + } + + public getColumnAreaItem(idx = 0): Locator { + return this.getAreas().nth(2).locator(`.${CLASS.field}.${CLASS.box}`).nth(idx); + } + + public getFilterAreaItem(idx = 0): Locator { + return this.getAreas().nth(3).locator(`.${CLASS.field}.${CLASS.box}`).nth(idx); + } + + public getDataAreaItem(idx = 0): Locator { + return this.getAreas().nth(4).locator(`.${CLASS.field}.${CLASS.box}`).nth(idx); + } + + public getDataFields(): Locator { + return this.getAreas().nth(4).locator(`.${CLASS.fields} .${CLASS.field}`); + } +} diff --git a/e2e/playwright/models/pivotGrid/filterHeaderArea.ts b/e2e/playwright/models/pivotGrid/filterHeaderArea.ts new file mode 100644 index 000000000000..e6c01d230041 --- /dev/null +++ b/e2e/playwright/models/pivotGrid/filterHeaderArea.ts @@ -0,0 +1,23 @@ +import type { Locator } from '@playwright/test'; +import HeaderFilter from './headerFilter'; + +const CLASSES = { + header: 'dx-pivotgrid-fields-area', + field: 'dx-area-field', +}; + +export default class FilterHeaderArea { + public readonly element: Locator; + + constructor(selector: Locator) { + this.element = selector.locator(`.${CLASSES.header}`); + } + + public getField(idx = 0): Locator { + return this.element.locator(`.${CLASSES.field}`).nth(idx); + } + + public getHeaderFilterIcon(idx = 0): HeaderFilter { + return new HeaderFilter(this.getField(idx)); + } +} diff --git a/e2e/playwright/models/pivotGrid/headerFilter.ts b/e2e/playwright/models/pivotGrid/headerFilter.ts new file mode 100644 index 000000000000..144350b85c83 --- /dev/null +++ b/e2e/playwright/models/pivotGrid/headerFilter.ts @@ -0,0 +1,17 @@ +import type { Locator } from '@playwright/test'; + +const CLASSES = { + icon: 'dx-header-filter', +}; + +export default class HeaderFilter { + public readonly element: Locator; + + constructor(selector: Locator) { + this.element = selector.locator(`.${CLASSES.icon}`); + } + + public ariaLabel(): Promise { + return this.element.getAttribute('aria-label'); + } +} diff --git a/e2e/playwright/models/pivotGrid/index.ts b/e2e/playwright/models/pivotGrid/index.ts new file mode 100644 index 000000000000..350ef4862aaf --- /dev/null +++ b/e2e/playwright/models/pivotGrid/index.ts @@ -0,0 +1,106 @@ +import type { Locator } from '@playwright/test'; +import type { WidgetName } from '../types'; +import Widget from '../internal/widget'; +import Popup from '../popup'; +import ColumnHeaderArea from './columnHeaderArea'; +import ColumnsArea from './columnsArea'; +import DataHeaderArea from './dataHeaderArea'; +import FieldChooser from './fieldChooser'; +import FilterHeaderArea from './filterHeaderArea'; +import Overlay from './overlay'; +import RowHeaderArea from './rowHeaderArea'; +import RowsArea from './rowsArea'; + +const CLASS = { + fieldChooser: 'dx-pivotgridfieldchooser', + fieldChooserButton: 'dx-pivotgrid-field-chooser-button', + fieldChooserPopup: 'dx-fieldchooser-popup', + exportBtn: 'dx-pivotgrid-export-button', + loadPanel: 'dx-loadpanel', +}; + +export default class PivotGrid extends Widget { + // eslint-disable-next-line class-methods-use-this + public getName(): WidgetName { return 'dxPivotGrid'; } + + public addWidgetPrefix(className: string): string { + return Widget.addClassPrefix(this.getName(), className); + } + + public async scrollTo(options: { top?: number; left?: number }): Promise { + await this.element.evaluate( + // eslint-disable-next-line no-underscore-dangle + (element, offset) => $(element).data('dxPivotGrid')._dataArea._getScrollable().scrollTo(offset), + options, + ); + } + + public async scrollBy(options: { top?: number; left?: number }): Promise { + await this.element.evaluate( + // eslint-disable-next-line no-underscore-dangle + (element, offset) => $(element).data('dxPivotGrid')._dataArea._getScrollable().scrollBy(offset), + options, + ); + } + + public getColumnsAreaScrollLeft(): Promise { + return this.element.evaluate( + // eslint-disable-next-line no-underscore-dangle + (element) => $(element).data('dxPivotGrid')._columnsArea._getScrollable().scrollLeft(), + ); + } + + public getRowsAreaScrollTop(): Promise { + return this.element.evaluate( + // eslint-disable-next-line no-underscore-dangle + (element) => $(element).data('dxPivotGrid')._rowsArea._getScrollable().scrollTop(), + ); + } + + public getFieldChooserButton(): Locator { + return this.element.locator(`.${CLASS.fieldChooserButton}`); + } + + public getFieldChooserPopup(): Popup { + return new Popup(this.page, this.element.locator(`.${CLASS.fieldChooserPopup}`)); + } + + public getFieldChooser(): FieldChooser { + return new FieldChooser( + this.page, + this.getFieldChooserPopup().getWrapper().locator(`.${CLASS.fieldChooser}`), + ); + } + + public getExportButton(): Locator { + return this.element.locator(`.${CLASS.exportBtn}`); + } + + public getColumnHeaderArea(): ColumnHeaderArea { + return new ColumnHeaderArea(this.page, this.element); + } + + public getFilterHeaderArea(): FilterHeaderArea { + return new FilterHeaderArea(this.element); + } + + public getRowHeaderArea(): RowHeaderArea { + return new RowHeaderArea(this.element); + } + + public getRowsArea(idx?: number): RowsArea { + return new RowsArea(this.element, idx); + } + + public getColumnsArea(idx?: number): ColumnsArea { + return new ColumnsArea(this.element, idx); + } + + public getDataHeaderArea(): DataHeaderArea { + return new DataHeaderArea(this.element); + } + + public getLoadPanel(): Overlay { + return new Overlay(this.page.locator(`.${CLASS.loadPanel}`).first()); + } +} diff --git a/e2e/playwright/models/pivotGrid/overlay.ts b/e2e/playwright/models/pivotGrid/overlay.ts new file mode 100644 index 000000000000..df9951a37b5c --- /dev/null +++ b/e2e/playwright/models/pivotGrid/overlay.ts @@ -0,0 +1,18 @@ +import type { Locator } from '@playwright/test'; +import { hasClass } from '../internal/hasClass'; + +const CLASS = { + invisible: 'dx-state-invisible', +}; + +export default class Overlay { + public readonly element: Locator; + + constructor(selector: Locator) { + this.element = selector; + } + + public isInvisible(): Promise { + return hasClass(this.element, CLASS.invisible); + } +} diff --git a/e2e/playwright/models/pivotGrid/rowHeaderArea.ts b/e2e/playwright/models/pivotGrid/rowHeaderArea.ts new file mode 100644 index 000000000000..9199af85df8a --- /dev/null +++ b/e2e/playwright/models/pivotGrid/rowHeaderArea.ts @@ -0,0 +1,23 @@ +import type { Locator } from '@playwright/test'; +import HeaderFilter from './headerFilter'; + +const CLASSES = { + rowHeader: 'dx-area-description-cell', + field: 'dx-area-field', +}; + +export default class RowHeaderArea { + public readonly element: Locator; + + constructor(selector: Locator) { + this.element = selector.locator(`.${CLASSES.rowHeader}`); + } + + public getField(idx = 0): Locator { + return this.element.locator(`.${CLASSES.field}`).nth(idx); + } + + public getHeaderFilterIcon(idx = 0): HeaderFilter { + return new HeaderFilter(this.getField(idx)); + } +} diff --git a/e2e/playwright/models/pivotGrid/rowsArea.ts b/e2e/playwright/models/pivotGrid/rowsArea.ts new file mode 100644 index 000000000000..2fb54e1093c9 --- /dev/null +++ b/e2e/playwright/models/pivotGrid/rowsArea.ts @@ -0,0 +1,22 @@ +import type { Locator } from '@playwright/test'; + +const CLASSES = { + root: 'dx-pivotgrid-vertical-headers', +}; + +export default class RowsArea { + public readonly element: Locator; + + constructor(selector: Locator, idx?: number) { + this.element = selector.locator(`.${CLASSES.root}`).nth(idx ?? 0); + } + + public getCell(idx = 0): Locator { + return this.element.locator('td').nth(idx); + } + + public getCellByPosition(rowIdx = 0, cellIdx = 0): Locator { + return this.element.locator('tr').nth(rowIdx).locator('td') + .nth(cellIdx); + } +} diff --git a/e2e/playwright/models/popup.ts b/e2e/playwright/models/popup.ts new file mode 100644 index 000000000000..9502b9995316 --- /dev/null +++ b/e2e/playwright/models/popup.ts @@ -0,0 +1,47 @@ +import type { Locator } from '@playwright/test'; +import type { WidgetName } from './types'; +import Button from './button'; +import Overlay from './overlay'; + +const CLASS = { + button: 'dx-button', + topToolbar: 'dx-popup-title', + bottomToolbar: 'dx-popup-bottom', + closeButton: 'dx-closebutton', + doneButton: 'dx-popup-done', + cancelButton: 'dx-popup-cancel', +}; + +export default class Popup extends Overlay { + public static className = '.dx-popup-wrapper'; + + // eslint-disable-next-line class-methods-use-this + public getName(): WidgetName { return 'dxPopup'; } + + public getToolbar(): Locator { + return this.getWrapper().locator(`.${CLASS.topToolbar}`); + } + + public getBottomToolbar(): Locator { + return this.getWrapper().locator(`.${CLASS.bottomToolbar}`); + } + + public getButton(index = 0): Button { + return new Button(this.page, this.getWrapper().locator(`.${CLASS.button}`).nth(index)); + } + + public getCloseButton(): Button { + return new Button(this.page, this.getWrapper().locator(`.${CLASS.closeButton}`)); + } + + public getApplyButton(): Button { + return new Button(this.page, this.getWrapper().locator(`.${CLASS.button}.${CLASS.doneButton}`)); + } + + public getCancelButton(): Button { + return new Button( + this.page, + this.getWrapper().locator(`.${CLASS.button}.${CLASS.cancelButton}`), + ); + } +} diff --git a/e2e/playwright/models/treeView/index.ts b/e2e/playwright/models/treeView/index.ts new file mode 100644 index 000000000000..619010ee9195 --- /dev/null +++ b/e2e/playwright/models/treeView/index.ts @@ -0,0 +1,45 @@ +import type { Locator } from '@playwright/test'; +import type { WidgetName } from '../types'; +import Widget from '../internal/widget'; +import CheckBox from '../checkBox'; +import TreeViewNode from './treeViewNode'; + +const CLASS = { + node: 'dx-treeview-node', + scrollable: 'dx-scrollable', + selectAllItem: 'dx-treeview-select-all-item', + searchBar: 'dx-treeview-search', +}; + +export default class TreeView extends Widget { + // eslint-disable-next-line class-methods-use-this + public getName(): WidgetName { return 'dxTreeView'; } + + public getNodes(): Locator { + return this.element.locator(`.${CLASS.node}`); + } + + public getNode(index = 0): TreeViewNode { + return new TreeViewNode(this.page, this.getNodes().nth(index)); + } + + public getNodeItem(index = 0): Locator { + return this.getNodes().nth(index); + } + + public getSelectAllCheckBox(): CheckBox { + return new CheckBox(this.page, this.element.locator(`.${CLASS.selectAllItem}`)); + } + + public getSearchTextBox(): Locator { + return this.element.locator(`.${CLASS.searchBar}`); + } + + public getCheckBoxByNodeIndex(index = 0): CheckBox { + return this.getNode(index).getCheckBox(); + } + + public getScrollable(): Locator { + return this.element.locator(`.${CLASS.scrollable}`); + } +} diff --git a/e2e/playwright/models/treeView/treeViewNode.ts b/e2e/playwright/models/treeView/treeViewNode.ts new file mode 100644 index 000000000000..7eddfbfcd7cb --- /dev/null +++ b/e2e/playwright/models/treeView/treeViewNode.ts @@ -0,0 +1,32 @@ +import type { Locator, Page } from '@playwright/test'; +import { hasClass } from '../internal/hasClass'; +import CheckBox from '../checkBox'; + +const CLASS = { + focused: 'dx-state-focused', + checkbox: 'dx-checkbox', + expandButton: 'dx-treeview-toggle-item-visibility', +}; + +export default class TreeViewNode { + private readonly page: Page; + + public readonly element: Locator; + + constructor(page: Page, element: Locator) { + this.page = page; + this.element = element; + } + + public isFocused(): Promise { + return hasClass(this.element, CLASS.focused); + } + + public getCheckBox(): CheckBox { + return new CheckBox(this.page, this.element.locator(`.${CLASS.checkbox}`)); + } + + public getExpandButton(): Locator { + return this.element.locator(`.${CLASS.expandButton}`); + } +} diff --git a/e2e/playwright/tests/common/pivotGrid/contextMenu.spec.ts b/e2e/playwright/tests/common/pivotGrid/contextMenu.spec.ts new file mode 100644 index 000000000000..b9a8d0a2f537 --- /dev/null +++ b/e2e/playwright/tests/common/pivotGrid/contextMenu.spec.ts @@ -0,0 +1,93 @@ +import { test } from '../../../fixtures'; +import { createWidget } from '../../../helpers/createWidget'; +import { testScreenshot } from '../../../helpers/screenshots'; + +const CONTEXT_MENU_CLASS = 'dx-context-menu'; +const FIELD_CHOOSER_AREA_FIELDS_CLASS = 'dx-area-fields'; + +test('ContextMenu width should be adjusted to the width of the item text (T1106236)', { + tag: ['@generic.light'], +}, async ({ page }) => { + await createWidget(page, 'dxPivotGrid', { + width: 1000, + allowSortingBySummary: true, + allowSorting: true, + allowExpandAll: true, + showBorders: true, + fieldChooser: { + enabled: false, + }, + fieldPanel: { + showFilterFields: false, + allowFieldDragging: false, + visible: true, + }, + onContextMenuPreparing(e) { + if (e.field?.dataField === 'amount') { + const menuItems = [] as any; + + e.items.push({ text: 'Summary Type', items: menuItems }); + ['Sum', 'Avg', 'Min', 'Max'].forEach((summaryType) => { + const summaryTypeValue = summaryType.toLowerCase(); + const text = summaryType === 'Min' + ? 'Min - The box is too narrow, the item text does not fit inside.' + : summaryType; + menuItems.push({ + text, + value: summaryType.toLowerCase(), + selected: e.field.summaryType === summaryTypeValue, + }); + }); + } + }, + dataSource: { + fields: [{ + caption: 'Region', + width: 120, + dataField: 'region', + area: 'row', + }, { + caption: 'City', + dataField: 'city', + width: 150, + area: 'row', + }, { + dataField: 'date', + dataType: 'date', + area: 'column', + }, { + groupName: 'date', + groupInterval: 'year', + expanded: true, + }, { + caption: 'Relative Sales', + dataField: 'amount', + dataType: 'number', + summaryType: 'sum', + area: 'data', + summaryDisplayMode: 'percentOfColumnGrandTotal', + }], + store: [{ + id: 10887, + region: 'Africa', + country: 'Egypt', + city: 'Cairo', + amount: 500, + date: new Date('2015-05-26'), + }, { + id: 10888, + region: 'South America', + country: 'Argentina', + city: 'Buenos Aires', + amount: 780, + date: '2015-05-07', + }], + }, + }); + + await page.locator(`.${FIELD_CHOOSER_AREA_FIELDS_CLASS}`).nth(1).click({ button: 'right' }); + + await page.locator(`.${CONTEXT_MENU_CLASS}`).first().hover(); + + await testScreenshot(page, 'PivotGrid contextmenu width.png'); +}); diff --git a/e2e/playwright/tests/common/pivotGrid/etalons/PivotGrid contextmenu width (fluent.blue.light).png b/e2e/playwright/tests/common/pivotGrid/etalons/PivotGrid contextmenu width (fluent.blue.light).png new file mode 100644 index 0000000000000000000000000000000000000000..40a8fd6b4af8b67d9d88f53e47dfd8e77d15fafe GIT binary patch literal 44465 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 literal 0 HcmV?d00001 diff --git a/e2e/playwright/tests/common/pivotGrid/etalons/PivotGrid contextmenu width (generic.light).png b/e2e/playwright/tests/common/pivotGrid/etalons/PivotGrid contextmenu width (generic.light).png new file mode 100644 index 0000000000000000000000000000000000000000..0ea2a7f5b23b42fc240c61207fc421991b297aed GIT binary patch literal 30592 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 FUjQ~iz-#~j literal 0 HcmV?d00001 diff --git a/e2e/playwright/tests/common/pivotGrid/etalons/rows_do_not_overlap_data_fields_if_virtual_scrolling_enabled_T1210807 (fluent.blue.light).png b/e2e/playwright/tests/common/pivotGrid/etalons/rows_do_not_overlap_data_fields_if_virtual_scrolling_enabled_T1210807 (fluent.blue.light).png new file mode 100644 index 0000000000000000000000000000000000000000..916251e2a3be51c5223de06ffdaa0dc7bb70fd0a GIT binary patch literal 26023 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 R6by;GfE6_rQ1XwT{SUZ { + await createWidget(page, 'dxPivotGrid', { + dataSource: { + fields: [{ + caption: 'data A', + dataField: 'data_A', + }], + store: [], + }, + export: { + enabled: true, + }, + onExporting() { + // eslint-disable-next-line no-underscore-dangle + (window as any).__exportCalled = true; + }, + }); + + const pivotGrid = new PivotGrid(page, '#container'); + + await pivotGrid.getExportButton().click(); + + // eslint-disable-next-line no-underscore-dangle + const exportCalled = await page.evaluate(() => (window as any).__exportCalled as boolean); + + expect(exportCalled).toBe(true); +}); diff --git a/e2e/playwright/tests/common/pivotGrid/reference.spec.ts b/e2e/playwright/tests/common/pivotGrid/reference.spec.ts index a4dacd7dfdc0..e81598fa20e3 100644 --- a/e2e/playwright/tests/common/pivotGrid/reference.spec.ts +++ b/e2e/playwright/tests/common/pivotGrid/reference.spec.ts @@ -27,7 +27,7 @@ test('PivotGrid renders its areas and answers its options', THEMES, async ({ pag const pivotGrid = new PivotGrid(page, '#container'); - await expect(pivotGrid.dataArea).toBeVisible(); + await expect(pivotGrid.getRowsArea().getCell(0)).toBeVisible(); expect(await pivotGrid.option('rowHeaderLayout')).toBe('standard'); await pivotGrid.option('rowHeaderLayout', 'tree'); diff --git a/e2e/playwright/tests/common/pivotGrid/sort/etalons/T1173442_after_sort_with_summary_display_mode (fluent.blue.light).png b/e2e/playwright/tests/common/pivotGrid/sort/etalons/T1173442_after_sort_with_summary_display_mode (fluent.blue.light).png new file mode 100644 index 0000000000000000000000000000000000000000..0c9e4d801c2ff0d962902c3c91b2902fa54ecdae GIT binary patch literal 13563 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Νs zl48tl&7yM75(V)-CU*8tWE)w_HcwmnIGhj4xC{g651t%Ex7jI8P>1dKVy=0Vz&r~S zGCmLo7%mE)!?O(MpFkft^BOs8&Q_FaNhOU^V;?P*Q0CmN{6R>dyWKPw#*(P~+g{zb zB*Z=h*^Kb8LxS319-)+<&SRx73V}$l+#e4k1UH0BJCoTHBzNRzt%KN3BGXe>eX7ciGr15KWM)Po+(^4Hskh<$vJkWbJ)%iH0krb#8AtkcY#kXwGFnz z!JZuiz4~!tS{~5N%U=xQuBU&X`%FE9_i^J_wpMctI?nF#2phc*%>bjpXF)0Z2!U2} zUjx{v&b7L#CaCrBD57iM;p<#I7ps!T!9yi^mj`kLn5HHOF+SjEP73PJ+ry5jeSf2K z^q9U`(~HH%=Pw;q4JW>gFVL}by?kvbE5Fd}zV=@2dNs9i>ppawKtqww>4F>Md%CN3asH;k~SYJa0DK$0!* zey_?u@&zk&UKzLocv zWc(ZV{m}8~gJGVljI8!5sTHOM2d=X(kbGc2D;QRS9rLVd=;?; zYdk=Yqk2Xi0Km-^rw$!ysjuXO&6!PKY@SeY-+;E}V;vXArf}xTFwcO^tZzcF{8n5f z?og!qxun}i4`RU6q{~gcjt~`pds`cCi-BHPj42x$qvhT$Y`vN`BG9Xvf0|TOUCH#lP_LKj*v3n zjYy$P4e`g%u+7i6F;T;L#Hg8ys${deH$;HYz_(O#S+!`2wsiH@dgC+o*%YQb%1gMz zi}KE7l+%2zzh`>J0!H|4xaE+UQw6UgRH&H z*S)r6D!px!Wt1WoC_nl>^m@4T%6pxv+p?nxV!q`%`G>egYxI;5mG{7y&rxOn*_>ES zjR7u6vD|DL?6-ClXYHO>WXp4WE~m1wOMkFIVwr(&IN5hJ42<9l4z`&;^#JwoZ!;@z z!SCd6F*CdD#HFJ#uiA!NOWToFwq`GwsAMp+^twDTJM$N=U1-aiJ6&1c{GO*F&UtLo zRq*}A9%wz|46TUg`EFJE=(E!+fQBKew?yK(rXju>fAS@M6_{x^3Hl}fOv;2@B!4Ti z4J=hMBTdIjk}O;$VMMzX^!rO|sv~5z}h) zC_s0eS>Xd&Q=88XIg>bfMl%HlDM6H*2~dOths_9*LK&h)pY?>F_xx&%xcyHMANNqCHUKi~ULD zsSK>sX+t#GXWLrYc1vFKb{AVo$YY6IYPqq-5V5i0w@c*Ob}k; z;CmdJBibe?+w=?E*f{z~>p0ijJKbjEyKlSaTh5{3O!S-tYTi`q3V`-y!0ip}o*51i zpQWsIXqq2FU?FTFl7WseivU^G~DNW7QTKW8_AHF;3ij=P) z6mKM5p|X8b=tcUj>&^oQ2l%S3EiED}>u&g>#ly2L@h~+KqMkduv&b}ga+RbB1=W}v zjv!4$+i#lw=Inwg^DN2|t$@I;`%BY{_1^ShzP3Vm$dOfkrAIau;LB$gt_6*H%Wdtj z;)Tdu{BHXkdTc6JkGVto%RD>t73MoX4dvAhTOSX2)s=1>HRn8B8^jW(X`Lmx`Mvp! z@E#wMd8(v7{Z~32K>pEiVHO^@%j8p&32Q0(dSRy?QY6T>*(HtLWm+7I74LALNtg<@ zC+IeJNF)c#*UJ|@(@B-`9^+;umS*?3jqdpltX|Efr485>Gc;{p%W*%4hoE=M@*ckrw8G`{1ppToyQ$DRdr z(PnrTqNUQqrJFln)r}Z;+N|{pWwxq+NcA7o#{L6R=abt-`9Y7bZ91rBwHzJK+LDe1 zPj07oi2{$j+-z0A-fCmJVp^&7WsOI|zvZl+6PaosTs&X5S}1rw@Pw9A%WQHaLtGJ0 z{81HJ_$8xNkms<(-B3eYT&7KM3-o5tD(OR!AGU@PTpcO&gC(=!=iIEn=MOdL*iDja zL?}_&V_#Fvfs##5GH`H8pvY>;-O`QoNOuoBvnKaDuuYN&Z0E(r+LowMoeeAj~eSs6k$w!$Mum#Wuf z(<#THBrw~KMK>2!5rX$vC2?9iL}a9;MbcDSPjB{azQ7-+N4?Y5%0+5v)d80I{4%z} z^n%3QKP<8i12T1GgQ+;5$Y6AGlMI@~Jac;ZLyjZQ3HzHBAH1MgSlxvdbGz7(MHE#ar-y>LEx`qPJgq_4&&gu2+pppH2m*dlT?HX;He z(cosU5)9nTI;&r27D_KobICm~^Tw?D1UJ}3=Php@o{!knU%spKSH7XI3ylgaP$LE~ zUjL_VWbscCW0~y5|F9JE|C4t9FRA7d{g$^+@dOma<7-FD4hYAjmrIHmAcA^#*VRWJ Qf7wM*PEEG@mHCJN1u4~%G5`Po literal 0 HcmV?d00001 diff --git a/e2e/playwright/tests/common/pivotGrid/sort/sortWithSummaryDisplayMode_T1173442.spec.ts b/e2e/playwright/tests/common/pivotGrid/sort/sortWithSummaryDisplayMode_T1173442.spec.ts new file mode 100644 index 000000000000..d10887f177a0 --- /dev/null +++ b/e2e/playwright/tests/common/pivotGrid/sort/sortWithSummaryDisplayMode_T1173442.spec.ts @@ -0,0 +1,111 @@ +import { test } from '../../../../fixtures'; +import { createWidget } from '../../../../helpers/createWidget'; +import { testScreenshot } from '../../../../helpers/screenshots'; +import PivotGrid from '../../../../models/pivotGrid'; + +test('Should apply sort changes to the markup if the "summaryDisplayMode" is set', async ({ page }) => { + await createWidget(page, 'dxPivotGrid', { + allowSortingBySummary: true, + allowSorting: true, + fieldPanel: { + showFilterFields: false, + visible: true, + }, + dataSource: { + fields: [{ + dataField: 'row', + area: 'row', + }, { + dataField: 'column', + area: 'column', + }, { + dataField: 'value', + dataType: 'number', + summaryType: 'sum', + area: 'data', + summaryDisplayMode: 'percentVariation', + }], + store: [ + { + row: 'row_A', + column: 'column_A', + value: 100, + }, + { + row: 'row_A', + column: 'column_A', + value: 100, + }, + { + row: 'row_A', + column: 'column_B', + value: 150, + }, + { + row: 'row_A', + column: 'column_B', + value: 150, + }, + { + row: 'row_A', + column: 'column_C', + value: 200, + }, + { + row: 'row_A', + column: 'column_C', + value: 200, + }, + { + row: 'row_B', + column: 'column_A', + value: 100, + }, + { + row: 'row_B', + column: 'column_A', + value: 100, + }, + { + row: 'row_B', + column: 'column_B', + value: 150, + }, + { + row: 'row_B', + column: 'column_B', + date: '2022-01-02', + value: 150, + }, + { + row: 'row_B', + column: 'column_C', + value: 200, + }, + { + row: 'row_B', + column: 'column_C', + date: '2022-01-02', + value: 200, + }, + ], + }, + }); + + const pivotGrid = new PivotGrid(page, '#container'); + + await testScreenshot( + page, + 'T1173442_before_sort_with_summary_display_mode.png', + { element: pivotGrid.element }, + ); + + await pivotGrid.getColumnHeaderArea().getField().click(); + await pivotGrid.getRowHeaderArea().getField().click(); + + await testScreenshot( + page, + 'T1173442_after_sort_with_summary_display_mode.png', + { element: pivotGrid.element }, + ); +}); diff --git a/e2e/playwright/tests/common/pivotGrid/virtualScrolling_T1210807.spec.ts b/e2e/playwright/tests/common/pivotGrid/virtualScrolling_T1210807.spec.ts new file mode 100644 index 000000000000..d635f96190bf --- /dev/null +++ b/e2e/playwright/tests/common/pivotGrid/virtualScrolling_T1210807.spec.ts @@ -0,0 +1,92 @@ +import { expect, test } from '../../../fixtures'; +import { createWidget } from '../../../helpers/createWidget'; +import { testScreenshot } from '../../../helpers/screenshots'; +import PivotGrid from '../../../models/pivotGrid'; + +const createData = (count: number, innerCount: number): object[] => { + const result: object[] = []; + + for (let i = 0; i < count; i += 1) { + for (let j = 0; j < innerCount; j += 1) { + result.push({ + item: `item ${i}`, + date: new Date('2024-01-01'), + category: `category ${j}`, + innerA: j, + innerB: j, + }); + } + } + + return result; +}; + +test('Row fields overlap data fields if dataFieldArea is set to "row" and virtual scrolling is enabled (T1210807)', { + tag: ['@material.blue.light', '@generic.light'], +}, async ({ page }) => { + await createWidget(page, 'dxPivotGrid', { + allowExpandAll: true, + showBorders: true, + rowHeaderLayout: 'tree', + dataFieldArea: 'row', + height: 560, + scrolling: { + mode: 'virtual', + }, + dataSource: { + fields: [ + { + dataField: 'item', + area: 'row', + width: 120, + }, + { + dataField: 'category', + area: 'row', + width: 120, + }, + { + dataField: 'date', + dataType: 'date', + area: 'column', + groupInterval: 'year', + }, + { + dataField: 'innerA', + dataType: 'number', + summaryType: 'sum', + area: 'data', + }, + { + dataField: 'innerB', + dataType: 'number', + summaryType: 'sum', + area: 'data', + }, + ], + store: createData(50, 5), + }, + }); + + const pivotGrid = new PivotGrid(page, '#container'); + const firstHeaderRow = pivotGrid.getRowsArea(2).getCell(0); + + await firstHeaderRow.click(); + + // The virtual scroll extends its range as rows load, so one jump lands short of the end and + // where exactly it stops depends on the machine. Scroll until the offset stops moving. + let previousScrollTop = -1; + + await expect.poll(async () => { + await pivotGrid.scrollBy({ top: 30000 }); + + const scrollTop = await pivotGrid.getRowsAreaScrollTop(); + const settled = scrollTop === previousScrollTop; + + previousScrollTop = scrollTop; + + return settled; + }).toBe(true); + + await testScreenshot(page, 'rows_do_not_overlap_data_fields_if_virtual_scrolling_enabled_T1210807.png', { element: pivotGrid.element }); +}); From e77800610a8cc7ac34b99dc35aa4d1d97fbdc9c9 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Mon, 7 Sep 2026 11:19:51 -0300 Subject: [PATCH 20/22] Give the Playwright PivotGrid tests the owners the TestCafe ones have Claude-Session: https://claude.ai/code/session_01SLZQZ92qqekVtCZBc87Ajg --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 34458f09c6b3..4ab06255e7ec 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -145,6 +145,8 @@ /packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/** @DevExpress/devextreme-scheduler /packages/testcafe-models/pivotGrid/** @DevExpress/devextreme-scheduler /e2e/testcafe-devextreme/tests/pivotGrid/** @DevExpress/devextreme-scheduler +/e2e/playwright/models/pivotGrid/** @DevExpress/devextreme-scheduler +/e2e/playwright/tests/common/pivotGrid/** @DevExpress/devextreme-scheduler /packages/devextreme-scss/scss/widgets/base/pivotGrid/** @DevExpress/devextreme-scheduler /packages/devextreme-scss/scss/widgets/fluent/pivotGrid/** @DevExpress/devextreme-scheduler From 6215b4c8a3e6e4d063aee2ad1e64b928595d53e4 Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Mon, 7 Sep 2026 13:39:38 -0300 Subject: [PATCH 21/22] Hand the theme and timezone to the container and size the local run to the machine Claude-Session: https://claude.ai/code/session_01SLZQZ92qqekVtCZBc87Ajg --- e2e/playwright/README.md | 5 +++++ e2e/playwright/docker/run.sh | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/e2e/playwright/README.md b/e2e/playwright/README.md index 7705a9a75a2f..29856d127208 100644 --- a/e2e/playwright/README.md +++ b/e2e/playwright/README.md @@ -65,8 +65,13 @@ dependencies and the DevExtreme build come from the host. ```bash docker/run.sh # the whole suite, as CI runs it docker/run.sh --ui # then open http://localhost:9323 +docker/run.sh --workers=3 # the concurrency of a particular job ``` +The screenshot budget is the strict one, but the run takes half the cores rather than the eight a +CI agent gets: that many browsers starve each other on a developer machine, and a timing-sensitive +test then fails for a reason that does not exist on CI. + Open the UI on `localhost` — the address Playwright prints, `0.0.0.0`, is not a secure origin and its trace viewer will not load there. diff --git a/e2e/playwright/docker/run.sh b/e2e/playwright/docker/run.sh index 7334c2cf1ab0..568ee305b2e2 100755 --- a/e2e/playwright/docker/run.sh +++ b/e2e/playwright/docker/run.sh @@ -21,7 +21,9 @@ docker build --platform linux/amd64 \ --build-arg "CHROME_VERSION=$CHROME_VERSION" \ -t devextreme-playwright-e2e "$REPO_ROOT/e2e/playwright/docker" +# THEME and TIMEZONE are what the matrix varies, so they are handed to the container as they are. RUN=(docker run --rm --platform linux/amd64 --shm-size=2gb --security-opt seccomp=unconfined + -e "THEME=${THEME:-}" -e "TIMEZONE=${TIMEZONE:-}" -v "$REPO_ROOT:/repo" -w /repo/e2e/playwright) if [ "$UI" = true ]; then @@ -30,4 +32,16 @@ if [ "$UI" = true ]; then node_modules/.bin/playwright test --ui-host=0.0.0.0 --ui-port="$UI_PORT" "$@" fi -exec "${RUN[@]}" -e CI=true devextreme-playwright-e2e node_modules/.bin/playwright test "$@" +# CI=true keeps the strict screenshot budget, but its worker default is sized for a CI agent: +# on a developer machine that many browsers starve each other and the timing-sensitive tests fail +# for no reason. Half the cores unless the caller says otherwise — to reproduce a specific job, +# pass its matrix concurrency, e.g. --workers=3. +WORKERS=--workers=50% +for arg in "$@"; do + case "$arg" in + --workers|--workers=*) WORKERS= ;; + esac +done + +exec "${RUN[@]}" -e CI=true devextreme-playwright-e2e \ + node_modules/.bin/playwright test ${WORKERS:+"$WORKERS"} "$@" From 2a7d061f5951373630187457eb51b2cfd5dba95a Mon Sep 17 00:00:00 2001 From: Aleksey Semikozov Date: Mon, 7 Sep 2026 15:36:54 -0300 Subject: [PATCH 22/22] Judge screenshots strictly wherever the render matches the agent Claude-Session: https://claude.ai/code/session_01SLZQZ92qqekVtCZBc87Ajg --- e2e/playwright/README.md | 5 +++-- e2e/playwright/docker/run.sh | 4 +++- e2e/playwright/playwright.config.ts | 10 +++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/e2e/playwright/README.md b/e2e/playwright/README.md index 29856d127208..4866c71070e4 100644 --- a/e2e/playwright/README.md +++ b/e2e/playwright/README.md @@ -33,8 +33,9 @@ as a regular expression. The static server for the test page starts on its own. comparer did. Etalons live in `etalons/` next to the test and carry the theme in the file name — `PivotGrid markup (fluent.blue.light).png` — the same convention the TestCafe run uses. -The pixel budget is asymmetric on purpose: **CI is the source of truth** (`maxDiffPixelRatio` -0.001, `threshold` 0.1) and a local run is a sanity check (0.05 / 0.2). +The pixel budget follows the renderer: the agent and the container draw the same pixels and are +both judged strictly (`maxDiffPixelRatio` 0.001, `threshold` 0.1), the UI mode included. A run on +the host is lenient (0.05 / 0.2) because its comparison is not trustworthy anyway — see below. Run the screenshot tests in the container and nowhere else. The etalons are Linux renders, and a macOS or Windows one differs in text metrics — the image comes out a different size, which no diff --git a/e2e/playwright/docker/run.sh b/e2e/playwright/docker/run.sh index 568ee305b2e2..a909ce11f8af 100755 --- a/e2e/playwright/docker/run.sh +++ b/e2e/playwright/docker/run.sh @@ -22,8 +22,10 @@ docker build --platform linux/amd64 \ -t devextreme-playwright-e2e "$REPO_ROOT/e2e/playwright/docker" # THEME and TIMEZONE are what the matrix varies, so they are handed to the container as they are. +# DX_CONTAINER says the render matches the agent, which is what the strict screenshot budget keys +# on — it rides here rather than in the Dockerfile so the image stays shareable with other packages. RUN=(docker run --rm --platform linux/amd64 --shm-size=2gb --security-opt seccomp=unconfined - -e "THEME=${THEME:-}" -e "TIMEZONE=${TIMEZONE:-}" + -e "THEME=${THEME:-}" -e "TIMEZONE=${TIMEZONE:-}" -e DX_CONTAINER=1 -v "$REPO_ROOT:/repo" -w /repo/e2e/playwright) if [ "$UI" = true ]; then diff --git a/e2e/playwright/playwright.config.ts b/e2e/playwright/playwright.config.ts index 6c5034a2eef1..2f637d27d648 100644 --- a/e2e/playwright/playwright.config.ts +++ b/e2e/playwright/playwright.config.ts @@ -7,9 +7,13 @@ import { const baseURL = `http://localhost:${SERVER_PORT}`; const [width, height] = DEFAULT_BROWSER_SIZE; -// The pixel budget is asymmetric on purpose: CI is the source of truth and the etalons are -// generated there, a local run only says whether the change is in the right ballpark. -const screenshotBudget = process.env.CI +// The budget follows the renderer, not the job: the agent and the local container draw the same +// pixels, so both are judged strictly — including the UI mode, where an etalon gets rewritten by +// hand and a lenient budget would let CI-breaking drift through. A host run stays lenient because +// its comparison is not trustworthy in the first place: another OS rasterises text differently. +const rendersLikeCI = !!process.env.CI || !!process.env.DX_CONTAINER; + +const screenshotBudget = rendersLikeCI ? { maxDiffPixelRatio: 0.001, threshold: 0.1 } : { maxDiffPixelRatio: 0.05, threshold: 0.2 };