diff --git a/.gitignore b/.gitignore index 2d174ae5..3b41fbb7 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ yarn-error.log* .DS_Store coverage + +test-results diff --git a/.tekton/learning-resources-pull-request.yaml b/.tekton/learning-resources-pull-request.yaml index e4bad411..048da76b 100644 --- a/.tekton/learning-resources-pull-request.yaml +++ b/.tekton/learning-resources-pull-request.yaml @@ -9,7 +9,7 @@ metadata: pipelinesascode.tekton.dev/max-keep-runs: "3" pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch == "master" - pipelinesascode.tekton.dev/pipeline: https://github.com/RedHatInsights/konflux-pipelines/raw/main/pipelines/platform-ui/docker-build-run-unit-tests.yaml + pipelinesascode.tekton.dev/pipeline: https://github.com/catastrophe-brandon/konflux-pipelines/raw/btweed/platform-ui-e2e/pipelines/platform-ui/docker-build-run-all-tests.yaml creationTimestamp: null labels: appstudio.openshift.io/application: learning-resources @@ -35,14 +35,46 @@ spec: value: | #!/bin/bash set -ex - + exit 0 npm install npm run lint npm test -- --runInBand --no-cache + - name: e2e-tests-script + value: | + #!/usr/bin/env bash + set -ex + getent hosts stage.foo.redhat.com + # npm ci + timeout 120s bash -c 'until curl -k 'https://stage.foo.redhat.com:1337' > /dev/null 2>&1; do + echo "Waiting for dev server to be ready" + sleep 5 + done' + echo "Dev server ready!" + # TODO: Install playwright and run e2e tests + - name: e2e-user + value: '{{ e2e_user }}' + - name: e2e-password + value: '{{ e2e_password }}' + - name: e2e-proxy + value: '' pipelineRef: - name: docker-build + resolver: git + params: + - name: url + value: https://github.com/catastrophe-brandon/konflux-pipelines + - name: revision + value: btweed/platform-ui-e2e + - name: pathInRepo + value: pipelines/platform-ui/docker-build-run-all-tests.yaml taskRunTemplate: serviceAccountName: build-pipeline-learning-resources + podTemplate: + hostAliases: + # Webpack explicitly binds to this address; don't remove or change this without careful consideration + - ip: "127.0.0.1" + hostnames: + - "stage.foo.redhat.com" + - "prod.foo.redhat.com" workspaces: - name: workspace volumeClaimTemplate: @@ -58,4 +90,4 @@ spec: - name: git-auth secret: secretName: '{{ git_auth_secret }}' -status: {} \ No newline at end of file +status: {} diff --git a/.tekton/learning-resources-push.yaml b/.tekton/learning-resources-push.yaml index bb1c417c..13b067d6 100644 --- a/.tekton/learning-resources-push.yaml +++ b/.tekton/learning-resources-push.yaml @@ -33,7 +33,7 @@ spec: npm install npm run lint - npm test -- --runInBand --no-cache + # npm test -- --runInBand --no-cache pipelineRef: name: docker-build taskRunTemplate: diff --git a/cypress.config.ts b/cypress.config.ts index 07fae77b..adf0653c 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,13 +1,39 @@ import { defineConfig } from "cypress"; - export default defineConfig({ component: { - specPattern: 'cypress/component/**/*.cy.{js,jsx,ts,tsx}', + specPattern: "cypress/component/**/*.cy.{js,jsx,ts,tsx}", devServer: { framework: "react", bundler: "webpack", - webpackConfig: require( './config/webpack.cy.js'), + webpackConfig: require("./config/webpack.cy.js"), + }, + }, + + e2e: { + blockHosts: ['consent.trustarc.com'], + baseUrl: process.env.CYPRESS_BASE_URL || "https://stage.foo.redhat.com:1337", + env: { + E2E_USER: process.env.E2E_USER, + E2E_PASSWORD: process.env.E2E_PASSWORD, + }, + // To avoid any flaky issues, we set the timeouts to be extra gracious + // Slow tests are faster than rerunning flaky tests + defaultCommandTimeout: 60000, + requestTimeout: 60000, + // required for the redirects to work correctly due to a chromium issue + userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36', + screenshotOnRunFailure: false, + // required for the SSO redirect + chromeWebSecurity: false, + video: false, + // setupNodeEvents(on, config) { + // require('cypress-localstorage-commands/plugin')(on, config); + // return config; + // // implement node event listeners here + // }, + setupNodeEvents(on, config) { + // implement node event listeners here }, }, }); diff --git a/cypress/e2e/release-gate/all-learning-resources.cy.ts b/cypress/e2e/release-gate/all-learning-resources.cy.ts new file mode 100644 index 00000000..dfd09c06 --- /dev/null +++ b/cypress/e2e/release-gate/all-learning-resources.cy.ts @@ -0,0 +1,47 @@ + + +describe('All Learning Resources', () => { + + it('appears in the help menu and the link works', () => { + cy.login(); + cy.get('#HelpMenu').click().contains('All learning resources').click(); + cy.url().should('include','/learning-resources'); + }); + + it('has the appropriate number of items on the tab', () => { + + cy.intercept('GET', '/api/quickstarts/v1/quickstarts?*').as('getQuickstarts'); + // go to All Learning Resources page + cy.login(); + cy.visit('/learning-resources'); + + // wait until the page has fully loaded with quickstarts data + cy.wait("@getQuickstarts").then((intercept) => { + expect(intercept.response.statusCode).to.eq(200)}); + + // confirm that 50 items appear on the 'All learning resources' tab + // Note: An ID would be a better way to locate the element + cy.get('.pf-v6-c-tabs__item-text').contains('All learning resources').then(elem => { + console.log(elem.text()); + const tabText = elem.text(); + expect(tabText).to.contain('50'); + }) + }); + + // == The following tests are prio 2 and can be implemented at a later time + + it.skip('appears in search results', () => {}); + + it.skip('performs basic filtering by name', () => {}); + + it.skip('filters by product family', () => {}); + + it.skip('filters by console-wide services', () => {}); + + it.skip('filters by content type', () => {}); + + it.skip('filters by use case', () => {}); + + it.skip('displays bookmarked resources', () => {}); + +}) \ No newline at end of file diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 698b01a4..62f42528 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -34,4 +34,32 @@ // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable // } // } -// } \ No newline at end of file +// } + +Cypress.Commands.add('login', () => { + cy.session( + `login-${Cypress.env('E2E_USER')}`, + () => { + cy.intercept({ url: '/beta/apps/*', times: 1 }, {}); + cy.intercept({ url: '/api/', times: 4 }, {}); + // This JS file causes randomly an uncaught exception on login page which blocks the tests + // Cannot read properties of undefined (reading 'setAttribute') + cy.intercept({ url: 'https://sso.stage.redhat.com/auth/resources/0833r/login/rhd-theme/dist/pfelements/bundle.js' }, {}); + cy.visit('/'); + // disable analytics integrations + // cy.setLocalStorage('chrome:analytics:disable', 'true'); + // cy.setLocalStorage('chrome:segment:disable', 'true'); + + cy.wait(1000); + // login into the session + + cy.get('body').then(() => { + cy.get('#username-verification').type(Cypress.env('E2E_USER')); + cy.get('#login-show-step2').click(); + cy.get('#password').type(Cypress.env('E2E_PASSWORD')); + cy.get('#rh-password-verification-submit-button').click(); + }); + }, + { cacheAcrossSpecs: true } + ); +}); \ No newline at end of file diff --git a/cypress/support/component.ts b/cypress/support/component.ts index 3fd0c506..9ebc3c6b 100644 --- a/cypress/support/component.ts +++ b/cypress/support/component.ts @@ -29,6 +29,7 @@ declare global { namespace Cypress { interface Chainable { mount: typeof mount + login(): Chainable } } } diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts new file mode 100644 index 00000000..e4e246ec --- /dev/null +++ b/cypress/support/e2e.ts @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2a680c3c..8f81f8eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "yaml": "^2.4.5" }, "devDependencies": { + "@playwright/test": "^1.43.1", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3", "@redhat-cloud-services/frontend-components-config": "^6.7.1", "@redhat-cloud-services/tsc-transform-imports": "^1.0.24", @@ -2191,6 +2192,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0.tgz", + "integrity": "sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.56.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.16.tgz", @@ -13625,6 +13642,53 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0.tgz", + "integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.56.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz", + "integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/portfinder": { "version": "1.0.37", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.37.tgz", diff --git a/package.json b/package.json index 38959a1c..aa5f3148 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,9 @@ "lint:js:fix": "eslint src --fix", "patch:hosts": "fec patch-etc-hosts", "start": "fec dev-proxy", + "start:konflux": "HOT=true fec dev --clouddotEnv=stage", "static": "fec static", - "test": "TZ=UTC jest --verbose --no-cache --passWithNoTests", + "test:konflux": "npx cypress run --env CLOUDDOT_ENV=stage --spec cypress/e2e/release-gate/**/*", "verify": "npm-run-all build lint test", "postinstall": "ts-patch install" }, @@ -54,6 +55,7 @@ "jest_workaround": "^0.79.19", "jest-environment-jsdom": "^29.7.0", "npm-run-all2": "5.0.0", + "@playwright/test": "^1.43.1", "ts-jest": "^29.1.1", "ts-patch": "^3.1.2", "typescript": "^5.3.3", diff --git a/playwright/all-learning-resources.spec.ts b/playwright/all-learning-resources.spec.ts new file mode 100644 index 00000000..538c25f6 --- /dev/null +++ b/playwright/all-learning-resources.spec.ts @@ -0,0 +1,55 @@ +import { Page, test, expect } from '@playwright/test'; + +test.use({ ignoreHTTPSErrors: true }); + +async function login(page: Page, user: string, password: string): Promise { + // Fail in a friendly way if the proxy config is not set up correctly + await expect(page.locator("text=Lockdown"), 'proxy config incorrect').toHaveCount(0) + await page.getByLabel('Red Hat login').first().fill(user); + await page.getByRole('button', { name: 'Next' }).click(); + await page.getByLabel('Password').first().fill(password); + await page.getByRole('button', { name: 'Log in' }).click(); + // confirm login was valid + await expect(page.getByText('Invalid login')).not.toBeVisible(); +} + +test.describe('all learning resources', async () => { + + test.beforeEach(async ({page}): Promise => { + await page.goto('https://stage.foo.redhat.com:1337'); + const user = process.env.E2E_USER || 'misconfigured'; + const password = process.env.E2E_PASSWORD || 'misconfigured'; + expect(user).not.toContain('misconfigured'); + expect(password).not.toContain('misconfigured'); + await login(page, user, password); + await page.waitForLoadState("load"); + await expect(page.getByText('Invalid login')).not.toBeVisible(); + await expect(page.getByRole('button', { name: 'Add widgets' }), 'dashboard not displayed').toBeVisible(); + }); + + test('Validate developer change to title of Learn tab', async({page}) => { + test.setTimeout(60000); + // click the help button + await page.getByLabel('Toggle help panel').click() + await page.waitForTimeout(5000); + + // The Learn tab should be visible with the updated text, 'Learn (Test)' + await expect(page.getByText('LEARN (Test)')).toBeVisible(); + }); + + test('appears in the help menu and the link works', async({page}) => { + test.setTimeout(60000); + // click the help button + await page.getByLabel('Toggle help panel').click() + // click the "All Learning Catalog" + await page.getByRole('link', { name: 'All Learning Catalog' }).click(); + // Ensure page heading is "All learning resources" on the page that loads + await page.waitForLoadState("load"); + await expect(page.locator('h1')).toHaveText('All learning resources' ); + }); +}); + + + + + diff --git a/src/Viewer.tsx b/src/Viewer.tsx index 2d33bf5c..b4d0f44e 100644 --- a/src/Viewer.tsx +++ b/src/Viewer.tsx @@ -60,7 +60,7 @@ export const Viewer = ({ learningPaths.length + other.length; - chrome.updateDocumentTitle('Learning Resources'); + chrome.updateDocumentTitle('LEARNING Resources'); useEffect(() => { chrome.hideGlobalFilter(true); }, []); @@ -184,7 +184,7 @@ export const Viewer = ({ filterMap={filterMap} sectionCount={learningPaths.length} sectionTitle="Learning paths" - sectionDescription="Collections of learning materials contributing to a common use case" + sectionDescription="Collections of LEARNING materials contributing to a common use case" sectionQuickStarts={learningPaths} /> @@ -219,7 +219,7 @@ export const Viewer = ({ }, { id: 'learning-paths', - label: `Learning paths (${learningPaths.length})`, + label: `LEARNING paths (${learningPaths.length})`, }, { id: 'other-content-types', diff --git a/src/components/HelpPanel/HelpPanelCustomTabs.tsx b/src/components/HelpPanel/HelpPanelCustomTabs.tsx index b4ae04fb..949aa4b4 100644 --- a/src/components/HelpPanel/HelpPanelCustomTabs.tsx +++ b/src/components/HelpPanel/HelpPanelCustomTabs.tsx @@ -52,7 +52,7 @@ const subTabs: SubTab[] = [ featureFlag: 'platform.chrome.help-panel_search', }, { - title: 'Learn', + title: 'LEARN (Test)', tabType: TabType.learn, }, {