diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml index d2ed5219e3..e3aea3a031 100644 --- a/.github/workflows/artifacts.yml +++ b/.github/workflows/artifacts.yml @@ -7,7 +7,6 @@ on: push: branches: - main - - develop release: types: [published] issue_comment: diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 6090d16a73..3c22b0b84b 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -11,7 +11,6 @@ on: push: branches: - main - - develop pull_request: types: [opened, synchronize, ready_for_review] @@ -142,3 +141,25 @@ jobs: - [ ] 📦 Build and push affected docker images' comment_tag: build-options GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + cypress-run: + name: End-to-end tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: Build the backend + run: sudo docker-compose -f support-services/docker-compose.yml up -d init + + - name: Install dependencies + run: | + npm ci + + - name: Run tests + run: npx nx run-many --target=e2e diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index ea8997cd08..0000000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: End-to-end tests -on: - push: - branches: - - main - - develop - pull_request: - types: [opened, synchronize, ready_for_review] - -jobs: - cypress-run: - name: Cypress test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build the backend - run: sudo docker-compose -f support-services/docker-compose.yml up -d init - - - name: install dependencies - run: | - npm ci - - - name: E2E tests - run: npx nx run-many --target=e2e diff --git a/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts b/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts index d2ebe0edd2..0a0c695e94 100644 --- a/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts +++ b/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts @@ -385,7 +385,7 @@ describe('dataset pages', () => { }) }) it('displays the full list after clicking two times on one filter', () => { - cy.get('gn-ui-data-downloads') + cy.get('datahub-record-downloads') .find('gn-ui-button') .children('button') .eq(1) diff --git a/apps/datahub-e2e/src/e2e/home.cy.ts b/apps/datahub-e2e/src/e2e/home.cy.ts index 12952662a2..46883d3de6 100644 --- a/apps/datahub-e2e/src/e2e/home.cy.ts +++ b/apps/datahub-e2e/src/e2e/home.cy.ts @@ -22,8 +22,7 @@ describe('header', () => { cy.get('[data-cy="addMoreBtn"]').should('be.visible') }) it('should display the orga and dataset link buttons', () => { - cy.get('[ng-reflect-title="datasets"]').should('be.visible') - cy.get('[ng-reflect-title="organisations"]').should('be.visible') + cy.get('gn-ui-figure').should('have.length', 2) }) describe('news feed display', () => { @@ -35,12 +34,14 @@ describe('header', () => { describe('link buttons display', () => { it('datasets : should display the icon', () => { - cy.get('[ng-reflect-title="datasets"]') + cy.get('gn-ui-figure') + .eq(0) .find('mat-icon') .should('have.text', ' folder_open ') }) it('organisations : should display the icon', () => { - cy.get('[ng-reflect-title="organisations"]') + cy.get('gn-ui-figure') + .eq(1) .find('mat-icon') .should('have.text', ' corporate_fare ') }) diff --git a/apps/datahub-e2e/src/support/commands.ts b/apps/datahub-e2e/src/support/commands.ts index 38595569a8..d9ccb51a63 100644 --- a/apps/datahub-e2e/src/support/commands.ts +++ b/apps/datahub-e2e/src/support/commands.ts @@ -7,114 +7,4 @@ // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** - -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Cypress { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface Chainable { - login(): void - clearFavorites(): void - - // interaction with gn-ui-dropdown-selector - openDropdown(): Chainable> - selectDropdownOption(value: string): void - getActiveDropdownOption(): Chainable> - } -} - -Cypress.Commands.add('login', () => { - // ignore error coming from GN - Cypress.on('uncaught:exception', (err) => { - if (err.message.includes('Jsonix')) return false - if (err.message.includes('postMessage')) return false - }) - - cy.visit('/geonetwork/srv/eng/catalog.signin?debug&redirect=blargz') // this will point to a 404 - cy.get('#inputUsername').type('admin', { force: true }) - cy.get('#inputPassword').type('admin', { force: true }) - cy.get('[name="gnSigninForm"]').submit() -}) - -/** - * This will most likely fail if the user is not logged in! - */ -Cypress.Commands.add('clearFavorites', () => { - cy.request({ - url: '/geonetwork/srv/api/me', - headers: { accept: 'application/json' }, - }) - .its('body') - .its('id') - .as('myId') - - cy.window().then(function () { - cy.request({ - url: `/geonetwork/srv/api/userselections/0/${this.myId}`, - headers: { accept: 'application/json' }, - }) - .its('body') - .as('favoritesId') - }) - - cy.getCookie('XSRF-TOKEN') - .its('value') - .then(function (token) { - const favoritesId = this.favoritesId || [] - cy.request({ - url: `/geonetwork/srv/api/userselections/0/${ - this.myId - }?uuid=${favoritesId.join('&uuid=')}`, - method: 'DELETE', - headers: { accept: 'application/json', 'X-XSRF-TOKEN': token }, - }) - }) -}) - -// previous value should be a component -Cypress.Commands.add( - 'openDropdown', - { prevSubject: true }, - (dropdownElement) => { - cy.get('body').click() // first click on the document to close other dropdowns - const width = dropdownElement.width() - const height = dropdownElement.height() - cy.wrap(dropdownElement).click(width - 10, height / 2) // click on the right size to avoid the label - return cy.get('.cdk-overlay-container').find('[role=listbox]') - } -) - -// previous value should be a component -Cypress.Commands.add( - 'selectDropdownOption', - { prevSubject: true }, - (dropdownElement, value: string) => { - cy.wrap(dropdownElement) - .openDropdown() - .find(`[data-cy-value="${value}"]`) - .click() - } -) - -// previous value should be a component -Cypress.Commands.add( - 'getActiveDropdownOption', - { prevSubject: true }, - (dropdownElement) => { - return cy.wrap(dropdownElement).openDropdown().find(`[data-cy-active]`) - } -) - -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +import '../../../../tools/e2e/commands' diff --git a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts index 14a5261f2e..d4c00e1bfb 100644 --- a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts @@ -42,17 +42,17 @@ describe('dashboard', () => { cy.visit('/records/all') cy.get('gn-ui-record-table') .find('.record-table-col') - .first() + .eq(1) .invoke('text') .then((list) => { originalFirstItem = list.trim() cy.get('.record-table-header').first().click() // Takes time to refresh results // eslint-disable-next-line cypress/no-unnecessary-waiting - // cy.wait(500) + cy.wait(500) cy.get('gn-ui-record-table') .find('.record-table-col') - .first() + .eq(1) .invoke('text') .then((list) => { newFirstItem = list.trim() @@ -71,7 +71,7 @@ describe('dashboard', () => { cy.visit('/records/all') cy.get('gn-ui-record-table') .find('.record-table-col') - .get('[type="checkbox"]') + .get('gn-ui-checkbox') .eq(2) .click() cy.get('.selected-records').contains('1 selected') @@ -81,7 +81,7 @@ describe('dashboard', () => { cy.visit('/records/all') cy.get('gn-ui-record-table') .find('.record-table-col') - .get('mat-checkbox.mat-primary') + .get('gn-ui-checkbox') .each(($checkbox) => cy.wrap($checkbox).click()) cy.get('.records-information').should( 'not.have.descendants', @@ -93,7 +93,7 @@ describe('dashboard', () => { cy.visit('/records/all') cy.get('gn-ui-record-table') .find('.record-table-col') - .get('mat-checkbox.mat-primary') + .get('gn-ui-checkbox') .first() .click() cy.get('.selected-records').contains('12 selected') diff --git a/apps/metadata-editor-e2e/src/e2e/home.cy.ts b/apps/metadata-editor-e2e/src/e2e/home.cy.ts index 2587cd057d..f9da3186b5 100644 --- a/apps/metadata-editor-e2e/src/e2e/home.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/home.cy.ts @@ -11,7 +11,7 @@ const gnBaseUrl = 'http://localhost:8080/geonetwork/srv/eng/' describe('avatar', () => { describe('display avatar for user without gravatar hash', () => { it('should display placeholder url', () => { - cy.loginGN('admin', 'admin', false) + cy.login('admin', 'admin', false) cy.visit(`${gnBaseUrl}admin.console#/organization`) cy.get('#gn-btn-user-add').click() cy.get('#username').type(fakeUser.username) @@ -33,7 +33,7 @@ describe('avatar', () => { .should('eq', 'https://www.gravatar.com/avatar/?d=mp') }) it('should display monsterid', () => { - cy.loginGN('admin', 'admin', false) + cy.login('admin', 'admin', false) cy.visit(`${gnBaseUrl}admin.console#/settings`) cy.get('[id="system/users/identicon"]').type( '{selectAll}gravatar:monsterid' @@ -49,7 +49,7 @@ describe('avatar', () => { }) describe('display avatar for user with hash', () => { it('should display the correct profile picture', () => { - cy.loginGN(fakeUser.username, fakeUser.password) + cy.login(fakeUser.username, fakeUser.password, true) cy.get('gn-ui-avatar') .children('img') .should('have.attr', 'src') diff --git a/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts b/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts index 620a5b5fff..ff578d8b68 100644 --- a/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts @@ -1,9 +1,9 @@ describe('my-org', () => { beforeEach(() => { - cy.loginGN('barbie', 'p4ssworD_', false) + cy.login('barbie', 'p4ssworD_', false) cy.intercept({ method: 'GET', - url: 'http://localhost:4200/geonetwork/srv/api/userselections/0/101', + url: '/geonetwork/srv/api/userselections/0/101', }).as('dataGetFirst') cy.visit(`/records/my-org`) cy.get('md-editor-dashboard-menu').find('a').first().click() diff --git a/apps/metadata-editor-e2e/src/support/app.po.ts b/apps/metadata-editor-e2e/src/support/app.po.ts deleted file mode 100644 index 00f556e103..0000000000 --- a/apps/metadata-editor-e2e/src/support/app.po.ts +++ /dev/null @@ -1 +0,0 @@ -export const getGreeting = () => cy.get('h1') diff --git a/apps/metadata-editor-e2e/src/support/commands.ts b/apps/metadata-editor-e2e/src/support/commands.ts index 85cc534619..d9ccb51a63 100644 --- a/apps/metadata-editor-e2e/src/support/commands.ts +++ b/apps/metadata-editor-e2e/src/support/commands.ts @@ -7,52 +7,4 @@ // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** - -/* eslint-disable cypress/no-unnecessary-waiting */ -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Cypress { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface Chainable { - // login(email: string, password: string): void - loginGN(username: string, password: string, redirect?: boolean): void - signOutGN(): void - } -} -// -// -- This is a parent command -- -/*Cypress.Commands.add('login', (email, password) => { - console.log('Custom command example: Login', email, password) -})*/ - -Cypress.Commands.add( - 'loginGN', - (username: string, password: string, redirect = true) => { - Cypress.on('uncaught:exception', (err) => { - if (err.message.includes('Jsonix')) return false - if (err.message.includes('postMessage')) return false - }) - - cy.visit('/geonetwork/srv/eng/catalog.signin?debug') // this will point to a 404 - cy.get('#inputUsername').type(username, { force: true }) - cy.get('#inputPassword').type(password, { force: true }) - cy.get('[name="gnSigninForm"]').submit() - if (redirect) cy.visit('/') - } -) - -Cypress.Commands.add('signOutGN', () => { - cy.visit('http://localhost:8080/geonetwork/srv/eng/catalog.search#/home') - cy.get('a[title="User details"]').click() - cy.get('a[title="Sign out"]').click() -}) -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) +import '../../../../tools/e2e/commands' diff --git a/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html b/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html index 4506863177..432dfe6b26 100644 --- a/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +++ b/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html @@ -67,6 +67,7 @@
{ + login(username?: string, password?: string, redirect?: boolean): void + signOut(): void + clearFavorites(): void + + // interaction with gn-ui-dropdown-selector + openDropdown(): Chainable> + selectDropdownOption(value: string): void + getActiveDropdownOption(): Chainable> + } +} + +Cypress.Commands.add( + 'login', + (username = 'admin', password = 'admin', redirect = false) => { + // first request to get the XSRF cookie + cy.request({ + method: 'GET', + url: '/geonetwork/srv/api/me', + headers: { + Accept: 'application/json', + }, + }) + cy.getCookie('XSRF-TOKEN').then((xsrfTokenCookie) => { + cy.request({ + method: 'POST', + url: '/geonetwork/signin', + body: `username=${username}&password=${password}&_csrf=${xsrfTokenCookie.value}`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }) + }) + if (redirect) cy.visit('/') + } +) + +Cypress.Commands.add('signOut', () => { + cy.visit('/geonetwork/srv/eng/catalog.search#/home') + cy.get('a[title="User details"]').click() + cy.get('a[title="Sign out"]').click() +}) + +/** + * This will most likely fail if the user is not logged in! + */ +Cypress.Commands.add('clearFavorites', () => { + cy.request({ + url: '/geonetwork/srv/api/me', + headers: { accept: 'application/json' }, + }) + .its('body') + .its('id') + .as('myId') + + cy.window().then(function () { + cy.request({ + url: `/geonetwork/srv/api/userselections/0/${this.myId}`, + headers: { accept: 'application/json' }, + }) + .its('body') + .as('favoritesId') + }) + + cy.getCookie('XSRF-TOKEN') + .its('value') + .then(function (token) { + const favoritesId = this.favoritesId || [] + cy.request({ + url: `/geonetwork/srv/api/userselections/0/${ + this.myId + }?uuid=${favoritesId.join('&uuid=')}`, + method: 'DELETE', + headers: { accept: 'application/json', 'X-XSRF-TOKEN': token }, + }) + }) +}) + +// previous value should be a component +Cypress.Commands.add( + 'openDropdown', + { prevSubject: true }, + (dropdownElement) => { + cy.get('body').click() // first click on the document to close other dropdowns + const width = dropdownElement.width() + const height = dropdownElement.height() + cy.wrap(dropdownElement).click(width - 10, height / 2) // click on the right size to avoid the label + return cy.get('.cdk-overlay-container').find('[role=listbox]') + } +) + +// previous value should be a component +Cypress.Commands.add( + 'selectDropdownOption', + { prevSubject: true }, + (dropdownElement, value: string) => { + cy.wrap(dropdownElement) + .openDropdown() + .find(`[data-cy-value="${value}"]`) + .click() + } +) + +// previous value should be a component +Cypress.Commands.add( + 'getActiveDropdownOption', + { prevSubject: true }, + (dropdownElement) => { + return cy.wrap(dropdownElement).openDropdown().find(`[data-cy-active]`) + } +) + +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) diff --git a/tools/e2e/tsconfig.json b/tools/e2e/tsconfig.json new file mode 100644 index 0000000000..1a77b2819a --- /dev/null +++ b/tools/e2e/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "../../dist/out-tsc", + "allowJs": true, + "types": ["cypress", "node"] + }, + "include": ["*.ts", "*.js", "cypress.config.ts"] +}