logonoff 4 месяцев назад
Родитель
Сommit
2dc6c3bb5e

+ 2 - 2
.gitignore

@@ -2,8 +2,8 @@
 **/dist
 **/dist
 **/.DS_Store
 **/.DS_Store
 .devcontainer/dev.env
 .devcontainer/dev.env
-integration-tests/videos
-integration-tests/screenshots
+integration-tests/.auth
+integration-tests/results
 
 
 # Jest
 # Jest
 coverage/
 coverage/

+ 9 - 8
AGENTS.md

@@ -16,7 +16,7 @@ This is a **template repository** for creating OpenShift Console dynamic plugins
 - PatternFly 6 (UI component library)
 - PatternFly 6 (UI component library)
 - Webpack 5 with Module Federation
 - Webpack 5 with Module Federation
 - react-i18next for internationalization
 - react-i18next for internationalization
-- Cypress for e2e testing
+- Playwright for e2e testing
 - Helm for deployment
 - Helm for deployment
 
 
 **Compatibility:** Requires OpenShift 4.12+ (uses ConsolePlugin CRD v1 API)
 **Compatibility:** Requires OpenShift 4.12+ (uses ConsolePlugin CRD v1 API)
@@ -81,7 +81,7 @@ tsconfig.json          # TypeScript config (strict: false currently)
 webpack.config.ts      # Module federation + build config
 webpack.config.ts      # Module federation + build config
 locales/               # i18n translation files
 locales/               # i18n translation files
 charts/                # Helm chart for deployment
 charts/                # Helm chart for deployment
-integration-tests/     # Cypress e2e tests
+integration-tests/     # Playwright e2e tests
 ```
 ```
 
 
 ## Development Workflow
 ## Development Workflow
@@ -98,8 +98,9 @@ integration-tests/     # Cypress e2e tests
 - Follow existing code patterns in the repo
 - Follow existing code patterns in the repo
 
 
 ### Testing
 ### Testing
-- `yarn test-cypress` - opens Cypress UI
-- `yarn test-cypress-headless` - runs Cypress in CI mode
+- `yarn test` - runs Jest unit tests
+- `yarn test-e2e` - opens Playwright in headed mode
+- `yarn test-e2e-headless` - runs Playwright in headless mode
 - Add e2e tests for new pages/features
 - Add e2e tests for new pages/features
 
 
 ## TypeScript Configuration
 ## TypeScript Configuration
@@ -200,9 +201,9 @@ See [Console Plugin SDK README](https://github.com/openshift/console/tree/master
 
 
 ## Testing Strategy
 ## Testing Strategy
 
 
-- **E2E tests (Cypress):** For user flows and page rendering
-- **Component tests:** Add when components have complex logic
-- **Test data attributes:** Use `data-test` attributes for selectors
+- **E2E tests (Playwright):** For user flows and page rendering
+- **Unit tests (Jest):** For component logic and plugin metadata
+- **Test data attributes:** Use `data-test` attributes for selectors (`testIdAttribute` is configured in `playwright.config.ts`)
 - Run tests locally before opening PRs
 - Run tests locally before opening PRs
 
 
 ## References
 ## References
@@ -219,5 +220,5 @@ See [Console Plugin SDK README](https://github.com/openshift/console/tree/master
 - **Add a page?** Update console-extensions.json + exposedModules + create component
 - **Add a page?** Update console-extensions.json + exposedModules + create component
 - **Style something?** Use PatternFly components and CSS variables, prefix custom classes
 - **Style something?** Use PatternFly components and CSS variables, prefix custom classes
 - **Add translations?** Use `t()` function, run `yarn i18n` after
 - **Add translations?** Use `t()` function, run `yarn i18n` after
-- **Test changes?** Run locally with `yarn start` + `yarn start-console`, add Cypress tests
+- **Test changes?** Run locally with `yarn start` + `yarn start-console`, add Playwright tests
 - **Deploy?** Build image, push to registry, install via Helm chart
 - **Deploy?** Build image, push to registry, install via Helm chart

+ 1 - 1
Dockerfile

@@ -1,7 +1,7 @@
 FROM registry.access.redhat.com/ubi9/nodejs-22:latest AS build
 FROM registry.access.redhat.com/ubi9/nodejs-22:latest AS build
 USER root
 USER root
 
 
-ENV CYPRESS_INSTALL_BINARY=0
+ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
 
 
 ADD . /usr/src/app
 ADD . /usr/src/app
 WORKDIR /usr/src/app
 WORKDIR /usr/src/app

+ 0 - 8
README.md

@@ -215,14 +215,6 @@ best practice is to prefix your CSS class names with your plugin name to avoid
 conflicts. Please don't disable these rules without understanding how they can
 conflicts. Please don't disable these rules without understanding how they can
 break console styles!
 break console styles!
 
 
-## Reporting
-
-Steps to generate reports
-
-1. In command prompt, navigate to root folder and execute the command `yarn run cypress-merge`
-2. Then execute command `yarn run cypress-generate`
-The cypress-report.html file is generated and should be in (/integration-tests/screenshots) directory.
-
 ## References
 ## References
 
 
 - [Console Plugin SDK README](https://github.com/openshift/console/tree/main/frontend/packages/console-dynamic-plugin-sdk)
 - [Console Plugin SDK README](https://github.com/openshift/console/tree/main/frontend/packages/console-dynamic-plugin-sdk)

+ 4 - 13
eslint.config.mjs

@@ -5,7 +5,7 @@ import prettier from 'eslint-plugin-prettier/recommended';
 import reactHooks from 'eslint-plugin-react-hooks';
 import reactHooks from 'eslint-plugin-react-hooks';
 import importX from 'eslint-plugin-import-x';
 import importX from 'eslint-plugin-import-x';
 import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript';
 import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript';
-import cypress from 'eslint-plugin-cypress';
+import playwright from 'eslint-plugin-playwright';
 import jest from 'eslint-plugin-jest';
 import jest from 'eslint-plugin-jest';
 import testingLibrary from 'eslint-plugin-testing-library';
 import testingLibrary from 'eslint-plugin-testing-library';
 import globals from 'globals';
 import globals from 'globals';
@@ -72,22 +72,13 @@ export default tseslint.config(
     },
     },
   },
   },
   {
   {
-    files: ['integration-tests/**/*.{ts,tsx,js}'],
+    ...playwright.configs['flat/recommended'],
+    files: ['integration-tests/**/*.ts'],
     ...tseslint.configs.disableTypeChecked,
     ...tseslint.configs.disableTypeChecked,
-    ...cypress.configs.recommended,
-    languageOptions: {
-      globals: {
-        require: 'readonly',
-        module: 'writable',
-      },
-    },
     rules: {
     rules: {
+      ...playwright.configs['flat/recommended'].rules,
       ...tseslint.configs.disableTypeChecked.rules,
       ...tseslint.configs.disableTypeChecked.rules,
-      ...cypress.configs.recommended.rules,
       'no-console': 'off',
       'no-console': 'off',
-      '@typescript-eslint/no-namespace': 'off',
-      '@typescript-eslint/no-require-imports': 'off',
-      '@typescript-eslint/no-unused-expressions': 'off',
     },
     },
   },
   },
   prettier,
   prettier,

+ 0 - 28
integration-tests/cypress.config.js

@@ -1,28 +0,0 @@
-const { defineConfig } = require('cypress');
-
-module.exports = defineConfig({
-  viewportWidth: 1920,
-  viewportHeight: 1080,
-  screenshotsFolder: './screenshots/screenshots',
-  videosFolder: './screenshots/videos',
-  video: true,
-  reporter: '../../node_modules/cypress-multi-reporters',
-  reporterOptions: {
-    configFile: 'reporter-config.json',
-  },
-  fixturesFolder: 'fixtures',
-  defaultCommandTimeout: 30000,
-  retries: {
-    runMode: 1,
-    openMode: 0,
-  },
-  e2e: {
-    setupNodeEvents(on, config) {
-      return require('./plugins/index.ts')(on, config);
-    },
-    specPattern: 'tests/**/*.cy.{js,jsx,ts,tsx}',
-    supportFile: 'support/index.ts',
-    testIsolation: false,
-    injectDocumentDomain: true,
-  },
-});

+ 0 - 5
integration-tests/fixtures/example.json

@@ -1,5 +0,0 @@
-{
-  "name": "Using fixtures to represent data",
-  "email": "hello@cypress.io",
-  "body": "Fixtures are a great way to mock data for responses to routes"
-}

+ 62 - 0
integration-tests/pages/login.ts

@@ -0,0 +1,62 @@
+import type { Locator, Page } from '@playwright/test';
+
+declare global {
+  interface Window {
+    SERVER_FLAGS?: {
+      authDisabled?: boolean;
+    };
+  }
+}
+
+export const KUBEADMIN_USERNAME = 'kubeadmin';
+
+export class LoginPage {
+  constructor(private readonly page: Page) {}
+
+  private async isAuthDisabled() {
+    return this.page.evaluate(() => window.SERVER_FLAGS?.authDisabled);
+  }
+
+  // Fill a field via CDP to avoid exposing the value in Playwright traces
+  // https://github.com/microsoft/playwright/issues/19992#issuecomment-4078945450
+  private async fillSensitive(locator: Locator, text: string) {
+    await locator.focus();
+    const cdpSession = await this.page.context().newCDPSession(this.page);
+    await cdpSession.send('Input.dispatchKeyEvent', {
+      type: 'keyDown',
+      key: 'a',
+      commands: ['selectAll'],
+    });
+    await cdpSession.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'a' });
+    await cdpSession.send('Input.insertText', { text });
+    await cdpSession.detach();
+  }
+
+  async login(
+    username: string = KUBEADMIN_USERNAME,
+    password: string = process.env.BRIDGE_KUBEADMIN_PASSWORD ?? '',
+  ) {
+    await this.page.context().clearCookies();
+    await this.page.goto('/');
+
+    if (await this.isAuthDisabled()) {
+      return;
+    }
+
+    await this.page.locator('[data-test-id="login"]').waitFor({ state: 'visible' });
+    await this.page.locator('#inputUsername').fill(username);
+    await this.fillSensitive(this.page.locator('#inputPassword'), password);
+    await this.page.locator('button[type=submit]').click();
+    await this.page.getByTestId('username').waitFor({ state: 'attached' });
+  }
+
+  async logout() {
+    if (await this.isAuthDisabled()) {
+      return;
+    }
+    await this.page.getByTestId('username').click();
+    await this.page.getByTestId('log-out').waitFor({ state: 'visible' });
+    // eslint-disable-next-line playwright/no-force-option -- dropdown may be covered by overlay
+    await this.page.getByTestId('log-out').click({ force: true });
+  }
+}

+ 0 - 26
integration-tests/plugins/index.ts

@@ -1,26 +0,0 @@
-const wp = require('@cypress/webpack-preprocessor');
-
-const config: Cypress.PluginConfig = (on, config) => {
-  const options = {
-    webpackOptions: {
-      resolve: {
-        extensions: ['.ts', '.tsx', '.js'],
-      },
-      module: {
-        rules: [
-          {
-            test: /\.tsx?$/,
-            loader: 'swc-loader',
-          },
-        ],
-      },
-    },
-  };
-  on('file:preprocessor', wp(options));
-  // `config` is the resolved Cypress config
-  config.baseUrl = `${process.env.BRIDGE_BASE_ADDRESS || 'http://localhost:9000/'}`;
-  config.env.BRIDGE_KUBEADMIN_PASSWORD = process.env.BRIDGE_KUBEADMIN_PASSWORD;
-  return config;
-};
-
-module.exports = config;

+ 0 - 14
integration-tests/reporter-config.json

@@ -1,14 +0,0 @@
-{
-    "reporterEnabled": "mocha-junit-reporter, mochawesome",
-    "mochaJunitReporterReporterOptions": {
-      "mochaFile": "./screenshots/junit_cypress-[hash].xml",
-      "toConsole": false
-    },
-    "mochawesomeReporterOptions": {
-      "reportDir": "./screenshots/",
-      "reportFilename": "cypress_report",
-      "overwrite": false,
-      "html": false,
-      "json": true
-    }
-  }

+ 8 - 5
integration-tests/support/index.ts

@@ -1,6 +1,9 @@
-import './login';
+import type { Page } from '@playwright/test';
+import { expect } from '@playwright/test';
 
 
-export const checkErrors = () =>
-  cy.window().then((win) => {
-    assert.isTrue(!win.windowError, win.windowError);
-  });
+export async function checkErrors(page: Page) {
+  const windowError = await page.evaluate(
+    () => (window as Window & { windowError?: string }).windowError,
+  );
+  expect(windowError, 'Console JS error detected').toBeUndefined();
+}

+ 0 - 71
integration-tests/support/login.ts

@@ -1,71 +0,0 @@
-declare global {
-  namespace Cypress {
-    interface Chainable {
-      login(username?: string, password?: string): Chainable<Element>;
-      logout(): Chainable<Element>;
-    }
-  }
-  interface Window {
-    SERVER_FLAGS?: {
-      authDisabled?: boolean;
-    };
-  }
-}
-
-export const KUBEADMIN_USERNAME = 'kubeadmin';
-
-// This will add 'cy.login(...)'
-// ex: cy.login('my-user', 'my-password')
-Cypress.Commands.add(
-  'login',
-  (
-    username: string = KUBEADMIN_USERNAME,
-    password: string = Cypress.env('BRIDGE_KUBEADMIN_PASSWORD'),
-  ) => {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const baseURL = Cypress.config('baseUrl')!;
-
-    // Make sure we clear the cookie in case a previous test failed to logout.
-    cy.clearCookie('openshift-session-token');
-
-    cy.visit(baseURL);
-
-    cy.session(
-      username,
-      () => {
-        // Check if auth is disabled (for a local development environment).
-        cy.window().then((win) => {
-          if (win.SERVER_FLAGS?.authDisabled) {
-            return;
-          }
-        });
-
-        cy.visit(baseURL);
-
-        cy.get('[data-test-id="login"]').should('be.visible');
-        cy.get('#inputUsername').type(username);
-        cy.get('#inputPassword').type(password);
-        cy.get('button[type=submit]').click();
-      },
-      {
-        cacheAcrossSpecs: true,
-        validate() {
-          cy.visit(baseURL);
-          cy.get('[data-test="username"]').should('exist');
-        },
-      },
-    );
-  },
-);
-
-Cypress.Commands.add('logout', () => {
-  // Check if auth is disabled (for a local development environment).
-  cy.window().then((win) => {
-    if (win.SERVER_FLAGS?.authDisabled) {
-      return;
-    }
-    cy.get('[data-test="username"]').click();
-    cy.get('[data-test="log-out"]').should('be.visible');
-    cy.get('[data-test="log-out"]').click({ force: true });
-  });
-});

+ 12 - 0
integration-tests/tests/auth.setup.ts

@@ -0,0 +1,12 @@
+import { test as setup } from '@playwright/test';
+import { LoginPage } from '../pages/login';
+
+// eslint-disable-next-line playwright/expect-expect -- setup test saves storageState, no assertions needed
+setup('authenticate', async ({ page }) => {
+  const loginPage = new LoginPage(page);
+  await loginPage.login();
+
+  await page.getByTestId('tour-step-footer-secondary').filter({ hasText: 'Skip tour' }).click();
+
+  await page.context().storageState({ path: 'integration-tests/.auth/user.json' });
+});

+ 0 - 89
integration-tests/tests/example-page.cy.ts

@@ -1,89 +0,0 @@
-import { checkErrors } from '../support';
-
-const PLUGIN_TEMPLATE_NAME = 'console-plugin-template';
-const PLUGIN_TEMPLATE_PULL_SPEC = Cypress.env('PLUGIN_TEMPLATE_PULL_SPEC');
-
-// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-export const isLocalDevEnvironment = Cypress.config('baseUrl')!.includes('localhost');
-
-const installHelmChart = (path: string) => {
-  // Install the plugin in a dedicated namespace using the helm chart from this repo
-  cy.exec(
-    `cd ../../console-plugin-template && ${path} upgrade -i ${PLUGIN_TEMPLATE_NAME} charts/openshift-console-plugin -n ${PLUGIN_TEMPLATE_NAME} --create-namespace --set plugin.image=${PLUGIN_TEMPLATE_PULL_SPEC}`,
-    {
-      failOnNonZeroExit: false,
-    },
-  ).then((result) => {
-    result.stderr && cy.log('Error installing helm chart: ', result.stderr);
-    result.stdout && cy.log('Successfully installed helm chart: ', result.stdout);
-  });
-
-  // Wait for the plugin deployment to be ready
-  cy.exec(
-    `oc rollout status -n ${PLUGIN_TEMPLATE_NAME} deploy/${PLUGIN_TEMPLATE_NAME} -w --timeout=300s`,
-    { timeout: 360000, failOnNonZeroExit: false },
-  );
-
-  // Wait for console pods to restart with the new plugin
-  cy.exec('oc rollout status -w deploy/console -n openshift-console --timeout=300s', {
-    timeout: 360000,
-    failOnNonZeroExit: false,
-  });
-
-  cy.visit('/k8s/cluster/operator.openshift.io~v1~Console/cluster/console-plugins');
-  cy.get(`[data-test="${PLUGIN_TEMPLATE_NAME}-status"]`).should('include.text', 'Loaded');
-};
-const deleteHelmChart = (path: string) => {
-  cy.exec(
-    `cd ../../console-plugin-template && ${path} uninstall ${PLUGIN_TEMPLATE_NAME} -n ${PLUGIN_TEMPLATE_NAME} && oc delete namespaces ${PLUGIN_TEMPLATE_NAME}`,
-    {
-      failOnNonZeroExit: false,
-    },
-  ).then((result) => {
-    cy.log('Error uninstalling helm chart: ', result.stderr);
-    cy.log('Successfully uninstalled helm chart: ', result.stdout);
-  });
-};
-
-describe('Console plugin template test', () => {
-  before(() => {
-    cy.login();
-    cy.get(`[data-test="tour-step-footer-secondary"]`).contains('Skip tour').click();
-    if (!isLocalDevEnvironment) {
-      console.log('this is not a local env, installing helm');
-
-      cy.exec('cd ../../console-plugin-template && ./install_helm.sh', {
-        failOnNonZeroExit: false,
-      }).then((result) => {
-        cy.log('Error installing helm binary: ', result.stderr);
-        cy.log('Successfully installed helm binary in "/tmp" directory: ', result.stdout);
-
-        installHelmChart('/tmp/helm');
-      });
-    } else {
-      console.log('this is a local env, not installing helm');
-
-      installHelmChart('helm');
-    }
-  });
-
-  afterEach(() => {
-    checkErrors();
-  });
-
-  after(() => {
-    if (!isLocalDevEnvironment) {
-      deleteHelmChart('/tmp/helm');
-    } else {
-      deleteHelmChart('helm');
-    }
-    cy.logout();
-  });
-
-  it('Verify the example page title', () => {
-    cy.get('[data-quickstart-id="qs-nav-home"]').click();
-    cy.get('[data-test="nav"]').contains('Plugin example').click();
-    cy.url().should('include', '/example');
-    cy.get('title').should('contain', 'Hello, plugin!');
-  });
-});

+ 73 - 0
integration-tests/tests/example-page.spec.ts

@@ -0,0 +1,73 @@
+import { execSync } from 'child_process';
+import { test, expect } from '@playwright/test';
+import { checkErrors } from '../support';
+
+const PLUGIN_TEMPLATE_NAME = 'console-plugin-template';
+// Defined in openshift/release ci-operator config as CYPRESS_PLUGIN_TEMPLATE_PULL_SPEC
+const PLUGIN_TEMPLATE_PULL_SPEC =
+  process.env.PLUGIN_TEMPLATE_PULL_SPEC ?? process.env.CYPRESS_PLUGIN_TEMPLATE_PULL_SPEC;
+
+const isLocalDevEnvironment = (process.env.BRIDGE_BASE_ADDRESS ?? 'http://localhost:9000').includes(
+  'localhost',
+);
+
+function exec(command: string, timeoutMs = 360000) {
+  try {
+    return execSync(command, { timeout: timeoutMs, encoding: 'utf-8' });
+  } catch (e) {
+    console.error('Command failed:', command, e);
+    return '';
+  }
+}
+
+function installHelmChart(helmPath: string) {
+  const result = exec(
+    `${helmPath} upgrade -i ${PLUGIN_TEMPLATE_NAME} charts/openshift-console-plugin -n ${PLUGIN_TEMPLATE_NAME} --create-namespace --set plugin.image=${PLUGIN_TEMPLATE_PULL_SPEC}`,
+  );
+  console.log('Helm install:', result);
+
+  exec(
+    `oc rollout status -n ${PLUGIN_TEMPLATE_NAME} deploy/${PLUGIN_TEMPLATE_NAME} -w --timeout=300s`,
+  );
+  exec('oc rollout status -w deploy/console -n openshift-console --timeout=300s');
+}
+
+function deleteHelmChart(helmPath: string) {
+  const result = exec(
+    `${helmPath} uninstall ${PLUGIN_TEMPLATE_NAME} -n ${PLUGIN_TEMPLATE_NAME} && oc delete namespaces ${PLUGIN_TEMPLATE_NAME}`,
+  );
+  console.log('Helm uninstall:', result);
+}
+
+test.describe('Console plugin template test', () => {
+  test.beforeAll(() => {
+    if (!isLocalDevEnvironment) {
+      console.log('this is not a local env, installing helm');
+      exec('./install_helm.sh');
+      installHelmChart('/tmp/helm');
+    } else {
+      console.log('this is a local env, not installing helm');
+      installHelmChart('helm');
+    }
+  });
+
+  test.afterEach(async ({ page }) => {
+    await checkErrors(page);
+  });
+
+  test.afterAll(() => {
+    if (!isLocalDevEnvironment) {
+      deleteHelmChart('/tmp/helm');
+    } else {
+      deleteHelmChart('helm');
+    }
+  });
+
+  test('Verify the example page title', async ({ page }) => {
+    await page.goto('/');
+    await page.locator('[data-quickstart-id="qs-nav-home"]').click();
+    await page.getByTestId('nav').getByText('Plugin example').click();
+    await expect(page).toHaveURL(/\/example/);
+    await expect(page).toHaveTitle(/Hello, plugin!/);
+  });
+});

+ 4 - 3
integration-tests/tsconfig.json

@@ -2,8 +2,9 @@
     "extends": "../tsconfig.json",
     "extends": "../tsconfig.json",
     "compilerOptions": {
     "compilerOptions": {
         "noEmit": true,
         "noEmit": true,
-        "types":["cypress","node"],
+        "types": ["node"],
         "isolatedModules": false
         "isolatedModules": false
     },
     },
-    "include": ["../node_modules/cypress", "./**/*.ts"]
-}
+    "include": ["./**/*.ts"],
+    "exclude": ["screenshots", ".auth"]
+}

+ 6 - 14
package.json

@@ -18,22 +18,20 @@
     "i18n": "./i18n-scripts/build-i18n.sh && node ./i18n-scripts/set-english-defaults.js",
     "i18n": "./i18n-scripts/build-i18n.sh && node ./i18n-scripts/set-english-defaults.js",
     "test": "LANG=en_US.UTF-8 jest",
     "test": "LANG=en_US.UTF-8 jest",
     "lint": "yarn eslint src integration-tests --fix --max-warnings 0 && stylelint 'src/**/*.css' --allow-empty-input --fix",
     "lint": "yarn eslint src integration-tests --fix --max-warnings 0 && stylelint 'src/**/*.css' --allow-empty-input --fix",
-    "test-cypress": "cd integration-tests && cypress open",
-    "test-cypress-headless": "cd integration-tests && node --max-old-space-size=4096 ../node_modules/.bin/cypress run --browser ${BRIDGE_E2E_BROWSER_NAME:-electron}",
-    "cypress-merge": "mochawesome-merge ./integration-tests/screenshots/cypress_report*.json > ./integration-tests/screenshots/cypress.json",
-    "cypress-generate": "marge -o ./integration-tests/screenshots/ -f cypress-report -t 'OpenShift Console Plugin Template Cypress Test Results' -p 'OpenShift Cypress Plugin Template Test Results' --showPassed false --assetsDir ./integration-tests/screenshots/cypress/assets ./integration-tests/screenshots/cypress.json",
-    "cypress-postreport": "yarn cypress-merge && yarn cypress-generate",
+    "test-e2e": "playwright test --headed",
+    "test-e2e-headless": "playwright test",
     "webpack": "node -r ts-node/register ./node_modules/.bin/webpack"
     "webpack": "node -r ts-node/register ./node_modules/.bin/webpack"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@babel/core": "^7.29.0",
     "@babel/core": "^7.29.0",
     "@babel/preset-env": "^7.29.3",
     "@babel/preset-env": "^7.29.3",
-    "@cypress/webpack-preprocessor": "^7.1.0",
     "@openshift-console/dynamic-plugin-sdk": "4.22-latest",
     "@openshift-console/dynamic-plugin-sdk": "4.22-latest",
     "@openshift-console/dynamic-plugin-sdk-webpack": "4.22-latest",
     "@openshift-console/dynamic-plugin-sdk-webpack": "4.22-latest",
     "@patternfly/react-core": "^6.4.3",
     "@patternfly/react-core": "^6.4.3",
     "@patternfly/react-icons": "^6.4.0",
     "@patternfly/react-icons": "^6.4.0",
     "@patternfly/react-table": "^6.4.3",
     "@patternfly/react-table": "^6.4.3",
+    "@playwright/browser-chromium": "^1.59.1",
+    "@playwright/test": "^1.59.1",
     "@swc/core": "^1.15.32",
     "@swc/core": "^1.15.32",
     "@swc/jest": "^0.2.39",
     "@swc/jest": "^0.2.39",
     "@testing-library/dom": "^10.4.1",
     "@testing-library/dom": "^10.4.1",
@@ -46,14 +44,12 @@
     "babel-loader": "^10.1.1",
     "babel-loader": "^10.1.1",
     "copy-webpack-plugin": "^14.0.0",
     "copy-webpack-plugin": "^14.0.0",
     "css-loader": "^7.1.4",
     "css-loader": "^7.1.4",
-    "cypress": "^15.14.2",
-    "cypress-multi-reporters": "^2.0.5",
     "eslint": "^9.7.0",
     "eslint": "^9.7.0",
     "eslint-config-prettier": "^10.1.8",
     "eslint-config-prettier": "^10.1.8",
     "eslint-import-resolver-typescript": "^4.4.4",
     "eslint-import-resolver-typescript": "^4.4.4",
-    "eslint-plugin-cypress": "^6.4.0",
     "eslint-plugin-import-x": "^4.16.2",
     "eslint-plugin-import-x": "^4.16.2",
     "eslint-plugin-jest": "^29.15.2",
     "eslint-plugin-jest": "^29.15.2",
+    "eslint-plugin-playwright": "^2.10.2",
     "eslint-plugin-prettier": "^5.5.5",
     "eslint-plugin-prettier": "^5.5.5",
     "eslint-plugin-react": "^7.37.5",
     "eslint-plugin-react": "^7.37.5",
     "eslint-plugin-react-hooks": "^7.1.1",
     "eslint-plugin-react-hooks": "^7.1.1",
@@ -65,10 +61,6 @@
     "jest": "^30.3.0",
     "jest": "^30.3.0",
     "jest-environment-jsdom": "^30.3.0",
     "jest-environment-jsdom": "^30.3.0",
     "jest-junit": "^17.0.0",
     "jest-junit": "^17.0.0",
-    "mocha": "^11.7.5",
-    "mocha-junit-reporter": "^2.2.1",
-    "mochawesome": "^7.1.4",
-    "mochawesome-merge": "^5.1.1",
     "pluralize": "^8.0.0",
     "pluralize": "^8.0.0",
     "prettier": "^3.8.3",
     "prettier": "^3.8.3",
     "prettier-stylelint": "^0.4.2",
     "prettier-stylelint": "^0.4.2",
@@ -88,7 +80,7 @@
     "webpack-dev-server": "^5.2.3"
     "webpack-dev-server": "^5.2.3"
   },
   },
   "dependenciesMeta": {
   "dependenciesMeta": {
-    "cypress": {
+    "@playwright/browser-chromium": {
       "built": true
       "built": true
     }
     }
   },
   },

+ 32 - 0
playwright.config.ts

@@ -0,0 +1,32 @@
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+  testDir: './integration-tests/tests',
+  timeout: 30000,
+  retries: 1,
+  use: {
+    baseURL: process.env.BRIDGE_BASE_ADDRESS ?? 'http://localhost:9000',
+    viewport: { width: 1920, height: 1080 },
+    screenshot: 'on',
+    ignoreHTTPSErrors: true,
+    video: 'retain-on-failure',
+    trace: 'on',
+    testIdAttribute: 'data-test',
+  },
+  projects: [
+    { name: 'setup', testMatch: /auth\.setup\.ts/ },
+    {
+      name: 'chromium',
+      use: {
+        browserName: 'chromium',
+        storageState: 'integration-tests/.auth/user.json',
+      },
+      dependencies: ['setup'],
+    },
+  ],
+  reporter: [
+    ['list'],
+    ['html', { outputFolder: 'integration-tests/results/html', open: 'never' }],
+    ['junit', { outputFile: 'integration-tests/results/junit-results.xml' }],
+  ],
+});

+ 6 - 6
test-prow-e2e.sh

@@ -3,16 +3,16 @@
 set -exuo pipefail
 set -exuo pipefail
 
 
 ARTIFACT_DIR=${ARTIFACT_DIR:=/tmp/artifacts}
 ARTIFACT_DIR=${ARTIFACT_DIR:=/tmp/artifacts}
-SCREENSHOTS_DIR=integration-tests/screenshots
+TEST_RESULTS_DIR=integration-tests/results
 INSTALLER_DIR=${INSTALLER_DIR:=${ARTIFACT_DIR}/installer}
 INSTALLER_DIR=${INSTALLER_DIR:=${ARTIFACT_DIR}/installer}
 
 
 function copyArtifacts {
 function copyArtifacts {
-  if [ -d "$ARTIFACT_DIR" ] && [ -d "$SCREENSHOTS_DIR" ]; then
-    if [[ -z "$(ls -A -- "$SCREENSHOTS_DIR")" ]]; then
+  if [ -d "$ARTIFACT_DIR" ] && [ -d "$TEST_RESULTS_DIR" ]; then
+    if [[ -z "$(ls -A -- "$TEST_RESULTS_DIR")" ]]; then
       echo "No artifacts were copied."
       echo "No artifacts were copied."
     else
     else
       echo "Copying artifacts from $(pwd)..."
       echo "Copying artifacts from $(pwd)..."
-      cp -r "$SCREENSHOTS_DIR" "${ARTIFACT_DIR}/screenshots"
+      cp -r "$TEST_RESULTS_DIR" "${ARTIFACT_DIR}"
     fi
     fi
   fi
   fi
 }
 }
@@ -33,5 +33,5 @@ if [ ! -d node_modules ]; then
   yarn install --immutable
   yarn install --immutable
 fi
 fi
 
 
-echo "Runs Cypress tests in headless mode"
-yarn test-cypress-headless
+echo "Runs Playwright tests in headless mode"
+yarn test-e2e-headless

Разница между файлами не показана из-за своего большого размера
+ 51 - 554
yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов