瀏覽代碼

Setup and config Cypress

CYRIL AJIEH 3 年之前
父節點
當前提交
bc406962dd

+ 4 - 0
.ci-operator.yaml

@@ -0,0 +1,4 @@
+build_root_image:
+  name: release
+  namespace: openshift
+  tag: nodejs-10-rhel7

+ 4 - 0
.gitignore

@@ -1,3 +1,7 @@
 node_modules/
 dist/
 .devcontainer/dev.env
+integration-tests/videos
+integration-tests/screenshots
+integration-tests/.DS_Store
+.DS_Store

+ 10 - 0
install_helm.sh

@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+USE_SUDO="false"
+HELM_INSTALL_DIR="/tmp"
+
+curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
+chmod 700 get_helm.sh
+source get_helm.sh
+
+rm -rf get_helm.sh

+ 17 - 0
integration-tests/.eslintrc

@@ -0,0 +1,17 @@
+{
+    "env": {
+      "cypress/globals": true,
+      "node": true
+    },
+    "extends": ["../.eslintrc.yml", "plugin:cypress/recommended"],
+    "plugins": ["cypress"],
+    "rules": {
+      "no-console": "off",
+      "no-namespace": "off",
+      "no-redeclare": "off",
+      "promise/catch-or-return": "off",
+      "promise/no-nesting": "off",
+      "@typescript-eslint/no-var-requires":"off",
+      "@typescript-eslint/no-namespace":"off"
+    }
+  }

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

@@ -0,0 +1,27 @@
+const { defineConfig } = require('cypress');
+
+module.exports = defineConfig({
+  viewportWidth: 1920,
+  viewportHeight: 1080,
+  screenshotsFolder: './integration-tests/screenshots',
+  videosFolder: './integration-tests/videos',
+  video: false,
+  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',
+    baseUrl: 'http://localhost:9000/',
+  },
+});

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

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

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

@@ -0,0 +1,27 @@
+import * as wp from '@cypress/webpack-preprocessor';
+
+module.exports = (on, config) => {
+  const options = {
+    webpackOptions: {
+      resolve: {
+        extensions: ['.ts', '.tsx', '.js'],
+      },
+      module: {
+        rules: [
+          {
+            test: /\.tsx?$/,
+            loader: 'ts-loader',
+            options: { happyPackMode: true, transpileOnly: true },
+          },
+        ],
+      },
+    },
+  };
+  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;
+};

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

@@ -0,0 +1,14 @@
+{
+    "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
+    }
+  }

+ 7 - 0
integration-tests/support/index.ts

@@ -0,0 +1,7 @@
+// Import commands.js using ES2015 syntax:
+import './login';
+
+export const checkErrors = () =>
+  cy.window().then((win: any) => {
+    assert.isTrue(!win.windowError, win.windowError);
+  });

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

@@ -0,0 +1,56 @@
+import { submitButton } from '../views/form';
+import { masthead } from '../views/masthead';
+
+declare global {
+  namespace Cypress {
+    interface Chainable {
+      login(
+        providerName?: string,
+        username?: string,
+        password?: string,
+      ): Chainable<Element>;
+      logout(): Chainable<Element>;
+    }
+  }
+}
+
+const KUBEADMIN_USERNAME = 'kubeadmin';
+
+// any command added below, must be added to global Cypress interface above
+
+// This will add 'cy.login(...)'
+// ex: cy.login('my-idp', 'my-user', 'my-password')
+Cypress.Commands.add(
+  'login',
+  (provider: string, username: string, password: string) => {
+    // Check if auth is disabled (for a local development environment).
+    cy.visit('http://localhost:9000/dashboards'); // visits baseUrl which is set in plugins/index.js
+    cy.window().then((win: any) => {
+      if (win.SERVER_FLAGS?.authDisabled) {
+        return;
+      }
+
+      // Make sure we clear the cookie in case a previous test failed to logout.
+      cy.clearCookie('openshift-session-token');
+
+      cy.get('#inputUsername').type(username || KUBEADMIN_USERNAME);
+      cy.get('#inputPassword').type(
+        password || Cypress.env('BRIDGE_KUBEADMIN_PASSWORD'),
+      );
+      cy.get(submitButton).click();
+      masthead.username.shouldBeVisible();
+    });
+  },
+);
+
+Cypress.Commands.add('logout', () => {
+  // Check if auth is disabled (for a local development environment).
+  cy.window().then((win: any) => {
+    if (win.SERVER_FLAGS?.authDisabled) {
+      return;
+    }
+    cy.get('[data-test="user-dropdown"]').click();
+    cy.get('[data-test="log-out"]').should('be.visible');
+    cy.get('[data-test="log-out"]').click({ force: true });
+  });
+});

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

@@ -0,0 +1,82 @@
+import { checkErrors } from '../support';
+
+const PLUGIN_TEMPLATE_NAME = 'console-plugin-template';
+const PLUGIN_TEMPLATE_PULL_SPEC = Cypress.env('PLUGIN_TEMPLATE_PULL_SPEC');
+export const isLocalDevEnvironment =
+  Cypress.config('baseUrl').includes('localhost');
+
+const installHelmChart = (path: string) => {
+  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: any) => {
+    cy.visit(`/example`);
+    cy.log('Error installing helm chart: ', result.stderr);
+    cy.log('Successfully installed helm chart: ', result.stdout);
+  });
+};
+
+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: any) => {
+    cy.log('Error uninstalling helm chart: ', result.stderr);
+    cy.log('Successfully uninstalled helm chart: ', result.stdout);
+  });
+};
+
+if (!Cypress.env('OPENSHIFT_CI') || Cypress.env('PLUGIN_TEMPLATE_PULL_SPEC')) {
+  describe('Console plugin template test', () => {
+    before(() => {
+      cy.login();
+
+      if (!isLocalDevEnvironment) {
+        console.log('this is not a local env, installig 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(() => {
+      cy.logout();
+      if (!isLocalDevEnvironment) {
+        deleteHelmChart('/tmp/helm');
+      } else {
+        deleteHelmChart('helm');
+      }
+    });
+
+    it('Verify the url', () => {
+      cy.url().should('include', '/example');
+    });
+    it('Verify the example page title', () => {
+      cy.get('[data-test="example-page-title"]').should(
+        'contain',
+        'Hello, Plugin!',
+      );
+    });
+  });
+}

+ 9 - 0
integration-tests/tsconfig.json

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

+ 1 - 0
integration-tests/views/form.ts

@@ -0,0 +1 @@
+export const submitButton = 'button[type=submit]';

+ 23 - 0
integration-tests/views/masthead.ts

@@ -0,0 +1,23 @@
+export const masthead = {
+  username: {
+    shouldBeVisible: () =>
+      cy
+        .get(
+          `[data-test=${Cypress.env(
+            'BRIDGE_KUBEADMIN_PASSWORD',
+          )} ? 'user-dropdown' : 'username'`,
+        )
+        .should('be.visible'),
+    shouldHaveText: (text: string) =>
+      cy
+        .get(
+          `[data-test=${Cypress.env(
+            'BRIDGE_KUBEADMIN_PASSWORD',
+          )} ? 'user-dropdown' : 'username'`,
+        )
+        .should('have.text', text),
+  },
+  clickMastheadLink: (path: string) => {
+    return cy.get(`[data-test=${path}`).click();
+  },
+};

+ 9 - 1
package.json

@@ -12,9 +12,12 @@
     "start-console": "./start-console.sh",
     "i18n": "i18next \"src/**/*.{js,jsx,ts,tsx}\" [-oc] -c i18next-parser.config.js",
     "ts-node": "ts-node -O '{\"module\":\"commonjs\"}'",
-    "lint": "eslint ./src --fix && stylelint \"src/**/*.css\" --allow-empty-input --fix"
+    "lint": "eslint ./src ./integration-tests --fix &&  stylelint \"src/**/*.css\" --allow-empty-input --fix",
+    "test-cypress": "cd integration-tests s && cypress open --env openshift=true",
+    "test-cypress-headless": "cd integration-tests && node --max-old-space-size=4096 ../node_modules/.bin/cypress run --env openshift=true --browser ${BRIDGE_E2E_BROWSER_NAME:=chrome}"
   },
   "devDependencies": {
+    "@cypress/webpack-preprocessor": "^5.15.5",
     "@openshift-console/dynamic-plugin-sdk": "0.0.12",
     "@openshift-console/dynamic-plugin-sdk-webpack": "0.0.7",
     "@patternfly/react-core": "4.221.3",
@@ -25,10 +28,15 @@
     "@typescript-eslint/eslint-plugin": "^5.29.0",
     "@typescript-eslint/parser": "^5.29.0",
     "css-loader": "^6.7.1",
+    "cypress": "^11.0.1",
+    "cypress-multi-reporters": "^1.6.2",
     "eslint": "^8.18.0",
     "eslint-config-prettier": "^8.5.0",
+    "eslint-plugin-cypress": "^2.12.1",
     "eslint-plugin-prettier": "^4.1.0",
     "eslint-plugin-react": "^7.30.1",
+    "mocha-junit-reporter": "^2.2.0",
+    "mochawesome": "^7.1.3",
     "prettier": "^2.7.1",
     "prettier-stylelint": "^0.4.2",
     "react": "^17.0.1",

+ 1 - 1
src/components/ExamplePage.tsx

@@ -13,7 +13,7 @@ export default function ExamplePage() {
   return (
     <>
       <Helmet>
-        <title>Hello, Plugin!</title>
+        <title data-test="example-page-title">Hello, Plugin!</title>
       </Helmet>
       <Page>
         <PageSection variant="light">

+ 28 - 0
test-prow-e2e.sh

@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+set -exuo pipefail
+
+ARTIFACT_DIR=${ARTIFACT_DIR:=/tmp/artifacts}
+SCREENSHOTS_DIR=integration-tests/screenshots
+INSTALLER_DIR=${INSTALLER_DIR:=${ARTIFACT_DIR}/installer}
+
+function copyArtifacts {
+  if [ -d "$ARTIFACT_DIR" ] && [ -d "$SCREENSHOTS_DIR" ]; then
+    echo "Copying artifacts from $(pwd)..."
+    cp -r "$SCREENSHOTS_DIR" "${ARTIFACT_DIR}/integration-tests/screenshots"
+  fi
+}
+
+
+trap copyArtifacts EXIT
+
+# don't log kubeadmin-password
+set +x
+BRIDGE_KUBEADMIN_PASSWORD="$(cat "${KUBEADMIN_PASSWORD_FILE:-${INSTALLER_DIR}/auth/kubeadmin-password}")"
+export BRIDGE_KUBEADMIN_PASSWORD
+set -x
+BRIDGE_BASE_ADDRESS="$(oc get consoles.config.openshift.io cluster -o jsonpath='{.status.consoleURL}')"
+export BRIDGE_BASE_ADDRESS
+
+echo "Runs Cypress tests in headless mode"
+yarn run test-cypress-headless

File diff suppressed because it is too large
+ 563 - 33
yarn.lock


Some files were not shown because too many files changed in this diff