diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 9fdf99146..c1bcdac8d 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,30 +1,62 @@ -# name: Test-e2e -# on: [push, pull_request] -# jobs: -# build: -# name: Test-e2e -# runs-on: ubuntu-latest -# timeout-minutes: 10 -# steps: -# - name: Check out code -# uses: actions/checkout@v4 -# - uses: actions/setup-python@v4 -# with: -# python-version: '3.11.4' -# cache: 'pip' -# - uses: actions/setup-node@v3 -# with: -# cache: 'yarn' -# node-version: 'v20.12.1' -# - name: Install dependencies -# run: | -# sudo apt-get update -# sudo apt-get install -y python3-setuptools python3-pip python3-virtualenv chromium-browser libgbm1 -# make install -# - name: DB setup -# run: | -# make migrate-upgrade -# python cre.py --upstream_sync -# # - name: Run app and e2e tests - # run: | - # make e2e +name: Cypress E2E Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + jobs: + cypress-run: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + python-version: '3.11' + cache: 'pip' + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'yarn' + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y python3-setuptools python3-pip python3-dev + + -name Install python dependencies + run: | + make install-deps-python + - name: Install Node.js dependencies + run: + yarn install --frozen-lockfile + - name: Setup database + run: + make migrate-upgrade + python cre.py --upstream_sync + + -name: Build frontend + run : + yarn build + -name: Start Flask server + run : + export FLASK_APP="${{github.workspace}}/cre.py" + export FLASK_CONFIG=development + export INSECURE_REQUESTS=1 + flask run --port=5000 & + sleep 5 + + -name: Run Cypress tests + run : + yarn cypress run + + -name:Upload screenshots and videos on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: Cypress screenshots and videos + path: cypress/screenshots, cypress/videos + + \ No newline at end of file diff --git a/application/frontend/src/test/basic-e2e.test.ts b/application/frontend/src/test/basic-e2e.test.ts index 7f5f8dd77..e4721dbd3 100644 --- a/application/frontend/src/test/basic-e2e.test.ts +++ b/application/frontend/src/test/basic-e2e.test.ts @@ -1,179 +1,100 @@ -// import puppeteer from 'puppeteer'; -// require('regenerator-runtime/runtime'); - -// describe('App.js', () => { -// var browser; -// var page; -// const debug = { -// // headless: false, -// // slowMo: 150, -// args: ['--no-sandbox', '--disable-setuid-sandbox'], // needed for docker, we trust the content of opencre.org -// }; -// const config = {}; -// beforeAll(async () => { -// jest.setTimeout(1000000); -// browser = await puppeteer.launch(debug); -// page = await browser.newPage(); -// page.setDefaultTimeout(15000); -// }); - -// it('contains the welcome text', { url: 'http://localhost:5000' }, async () => { -// await page.goto('http://localhost:5000'); -// await page.waitForSelector('#SearchBar'); -// const text = await page.$eval('#SearchBar', (e) => e.textContent); -// expect(text).toContain('Search'); -// }); - -// it('can search for random strs', { url: 'http://127.0.0.1:5000' }, async () => { -// await page.goto('http://127.0.0.1:5000'); -// await page.waitForSelector('#SearchBar'); -// await page.type('#SearchBar > div > input', 'asdf'); -// await page.click('#SearchBar > div > button'); -// await page.waitForSelector('.content'); -// const text = await page.$eval('.content', (e) => e.textContent); -// expect(text).toContain('No results match your search term'); -// }); - -// it( -// 'can search for cryptography using the free text method and it returns both Nodes and CRES', -// { url: 'http://127.0.0.1:5000' }, -// async () => { -// await page.goto('http://127.0.0.1:5000'); -// await page.waitForSelector('#SearchBar'); -// await page.type('#SearchBar > div > input', 'crypto'); -// await page.click('#SearchBar > div > button'); -// await page.waitForSelector('.content'); -// const text = await page.$eval('.content', (e) => e.textContent); -// expect(text).not.toContain('No results match your search term'); - -// await page.waitForSelector('.standard-page__links-container'); -// const results = await page.$$('.standard-page__links-container'); -// expect(results.length).toBeGreaterThan(1); - -// const cres = await page.$$('.cre-page >div>div>.standard-page__links-container'); -// expect(cres.length).toBeGreaterThan(1); - -// const docs = await page.$$('.cre-page >div>div:nth-child(2)>.standard-page__links-container'); -// expect(docs.length).toBeGreaterThan(1); -// } -// ); - -// it( -// 'can search for a standard by name, section and the standard page works as expected', -// { url: 'http://127.0.0.1:5000' }, -// async () => { -// await page.goto('http://127.0.0.1:5000/node/standard/ASVS'); -// await page.waitForSelector('.content'); -// const text = await page.$$('.content', (e) => e.textContent); -// expect(text).not.toContain('No results match your search term'); - -// await page.waitForSelector('.standard-page__links-container'); - -// // title match -// const page_title = await page.$eval('.standard-page__heading', (e) => e.textContent); -// expect(page_title).toContain('ASVS'); - -// // results -// const results = await page.$$('.standard-page__links-container'); -// expect(results.length).toBeGreaterThan(1); - -// // pagination -// const original_content = await page.content(); -// await page.click('a[type="pageItem"][value="2"]'); -// await page.waitForSelector('.content'); -// expect(await page.content()).not.toEqual(original_content); - -// // link to section -// await page.click('.standard-page__links-container>.title>a'); -// await page.waitForSelector('.content'); -// const url = await page.url(); -// expect(url).toContain('section'); -// const section = await page.$eval('.standard-page > span:nth-child(2)', (e) => e.textContent); -// expect(section).toContain('Reference:'); - -// // show reference -// const hrefs = await page.evaluate(() => -// Array.from(document.querySelectorAll('.section-page > a[href]'), (a) => a.getAttribute('href')) -// ); -// expect(hrefs[0]).toContain('https://'); - -// // link to at least one cre -// const cre_links = await page.$$('.cre-page__links-container > .title > a:nth-child(1)'); -// expect(cre_links.length).toBeGreaterThan(0); -// const cre_links_hrefs = await page.evaluate(() => -// Array.from(document.querySelectorAll('.cre-page__links-container > .title > a:nth-child(1)'), (a) => -// a.getAttribute('href') -// ) -// ); -// expect(cre_links_hrefs[0]).toContain('/cre/'); -// } -// ); - -// it('can search for a cre', { url: 'http://127.0.0.1:5000' }, async () => { -// await page.goto('http://127.0.0.1:5000'); -// await page.waitForSelector('#SearchBar'); -// await page.type('#SearchBar > div > input', '558-807'); -// await page.click('#SearchBar > div > button'); -// await page.waitForSelector('.content'); -// const text = await page.$$('.content', (e) => e.textContent); -// expect(text).not.toContain('No results match your search term'); - -// await page.waitForSelector('.standard-page__links-container'); - -// // title match -// const entry_title = await page.$eval('div.title.document-node', (e) => e.textContent); -// expect(entry_title).toContain('Mutually authenticate application and credential service provider'); - -// // results -// const results = await page.$$('.standard-page__links-container'); -// expect(results.length).toBe(1); - -// // // nesting -// await page.click('.dropdown'); -// const selector = -// '.standard-page__links-container>.document-node>.document-node__link-type-container:nth-child(2)'; -// await page.waitForSelector(selector); - -// const nested = await page.$$( -// '.standard-page__links-container>.document-node>.document-node__link-type-container>div>.accordion' -// ); -// expect(nested.length).toBeGreaterThan(1); -// }); - -// it('can filter', { url: 'http://127.0.0.1:5000' }, async () => { -// await page.goto('http://127.0.0.1:5000/cre/558-807?applyFilters=true&filters=asvs'); -// await page.waitForSelector('.cre-page__links-container'); -// // Get inner text -// const innerText = await page.evaluate( -// () => (document.querySelector('.cre-page__links-container') as HTMLElement)?.innerText -// ); -// expect(innerText).toContain('ASVS'); -// expect(innerText).toContain('CRE'); -// expect(innerText).not.toContain('NIST'); - -// // ensure case insensitive filtering -// await page.goto('http://127.0.0.1:5000/cre/558-807?applyFilters=true&filters=ASVS'); -// await page.waitForSelector('.cre-page__links-container'); -// const intxt = await page.evaluate( -// () => (document.querySelector('.cre-page__links-container') as HTMLElement)?.innerText -// ); -// expect(intxt).toContain('ASVS'); -// expect(intxt).toContain('CRE'); -// expect(intxt).not.toContain('NIST'); - -// const clearFilters = await page.evaluate( -// () => (document.querySelector('#clearFilterButton') as HTMLElement)?.innerText -// ); -// expect(clearFilters).toContain('Clear Filters'); -// }); - -// it('can smartlink', { url: 'http://127.0.0.1:5000' }, async () => { -// const response = await page.goto('http://127.0.0.1:5000/smartlink/standard/CWE/1002'); -// expect(response.url()).toBe('http://127.0.0.1:5000/node/standard/CWE/sectionid/1002'); - -// const redirectResponse = await page.goto('http://127.0.0.1:5000/smartlink/standard/CWE/404'); -// expect(redirectResponse.url()).toBe('https://cwe.mitre.org/data/definitions/404.html'); -// }); - -// afterAll(async () => await browser.close()); -// }); +/// +describe('OpenCRE App E2E Tests', () => { + + + beforeEach(() => { + // Visit home page before each test + cy.visit('/'); + }); + + it('contains the welcome text', () => { + cy.get('#SearchBar').should('exist').and('contain.text', 'Search'); + }); + + it('can search for random strings', () => { + cy.get('#SearchBar > div > input').type('asdf'); + cy.get('#SearchBar > div > button').click(); + cy.get('.content').should('contain.text', 'No results match your search term'); + }); + + it('can search for "crypto" and return Nodes and CRES', () => { + cy.get('#SearchBar > div > input').type('crypto'); + cy.get('#SearchBar > div > button').click(); + + cy.get('.content').should('not.contain.text', 'No results match your search term'); + + cy.get('.standard-page__links-container').should('have.length.greaterThan', 1); + cy.get('.cre-page .standard-page__links-container').should('have.length.greaterThan', 1); + cy.get('.cre-page div:nth-child(2) .standard-page__links-container').should('have.length.greaterThan', 1); + }); + + it('can search for a standard (ASVS) and verify page elements', () => { + cy.visit(`/node/standard/ASVS`); + + cy.get('.content').should('exist').and('not.contain.text', 'No results match your search term'); + cy.get('.standard-page__heading').should('contain.text', 'ASVS'); + cy.get('.standard-page__links-container').should('have.length.greaterThan', 1); + + // Pagination + cy.get('a[type="pageItem"][value="2"]').click(); + cy.get('.content').should('exist'); + + // Click first section link + cy.get('.standard-page__links-container > .title > a').first().click(); + cy.get('.content').should('exist'); + cy.url().should('contain', 'section'); + cy.get('.standard-page > span:nth-child(2)').should('contain.text', 'Reference:'); + + // Check reference links + cy.get('.section-page a[href]').first().should('have.attr', 'href').and('contain', 'https://'); + + // Verify at least one CRE link + cy.get('.cre-page__links-container > .title > a').first().should('exist'); + }); + + it('can search for a CRE (558-807) and verify content', () => { + cy.get('#SearchBar > div > input').type('558-807'); + cy.get('#SearchBar > div > button').click(); + + cy.get('.content').should('not.contain.text', 'No results match your search term'); + cy.get('div.title.document-node').should( + 'contain.text', + 'Mutually authenticate application and credential service provider' + ); + + cy.get('.standard-page__links-container').should('have.length', 1); + + // Check nested accordions + cy.get('.dropdown').click(); + cy.get( + '.standard-page__links-container > .document-node > .document-node__link-type-container > div > .accordion' + ).should('have.length.greaterThan', 1); + }); + + it('can filter CRE results', () => { + cy.visit(`/cre/558-807?applyFilters=true&filters=asvs`); + cy.get('.cre-page__links-container') + .should('contain.text', 'ASVS') + .and('contain.text', 'CRE') + .and('not.contain.text', 'NIST'); + + // Case-insensitive filter check + cy.visit(`/cre/558-807?applyFilters=true&filters=ASVS`); + cy.get('.cre-page__links-container') + .should('contain.text', 'ASVS') + .and('contain.text', 'CRE') + .and('not.contain.text', 'NIST'); + + cy.get('#clearFilterButton').should('contain.text', 'Clear Filters'); + }); + + it('can smartlink', () => { + cy.request(`/smartlink/standard/CWE/1002`).then((res) => { + expect(res.redirectedToUrl).to.include('/node/standard/CWE/sectionid/1002'); + }); + + cy.request(`smartlink/standard/CWE/404`).then((res) => { + expect(res.redirectedToUrl).to.eq('https://cwe.mitre.org/data/definitions/404.html'); + }); + }); +}); \ No newline at end of file diff --git a/application/frontend/src/test/cypress.config.ts b/application/frontend/src/test/cypress.config.ts new file mode 100644 index 000000000..7d59fb4a5 --- /dev/null +++ b/application/frontend/src/test/cypress.config.ts @@ -0,0 +1,12 @@ +import type { Cypress } from "cypress"; + +const config: Cypress.ConfigOptions = { + e2e: { + baseUrl: "http://localhost:9001", + setupNodeEvents(on, config) { + // node events + }, + }, +}; + +export default config; \ No newline at end of file diff --git a/application/frontend/src/test/cypress/e2e/e2e-smoke.cy.ts b/application/frontend/src/test/cypress/e2e/e2e-smoke.cy.ts new file mode 100644 index 000000000..9c8945b74 --- /dev/null +++ b/application/frontend/src/test/cypress/e2e/e2e-smoke.cy.ts @@ -0,0 +1,6 @@ +describe ('E2E setup smoke test', () => { + it('loads the OpenCRE homepage', () => { + cy.visit('http://localhost:9001') + cy.get('body').should('be.visible') + }) +}) \ No newline at end of file