diff --git a/.github/workflows/report-viewer-e2e.yml b/.github/workflows/report-viewer-e2e.yml index 0335bf8ad..064df6e7a 100644 --- a/.github/workflows/report-viewer-e2e.yml +++ b/.github/workflows/report-viewer-e2e.yml @@ -32,9 +32,27 @@ jobs: with: node-version: "18" - - name: Install and Test ๐Ÿงช + - name: Install and Build ๐Ÿ”ง working-directory: report-viewer run: | npm install - npx playwright install --with-deps - npm run test:e2e \ No newline at end of file + npm run build + + - name: Install playwright ๐Ÿ”ง + working-directory: report-viewer + run: npx playwright install --with-deps + + - name: Run tests ๐Ÿงช + working-directory: report-viewer + run: | + npm run test:e2e + + - name: Upload test results ๐Ÿ“ค + uses: actions/upload-artifact@v3 + if: always() + with: + name: test-results + path: | + report-viewer/test-results + report-viewer/playwright-report + retention-days: 30 \ No newline at end of file diff --git a/report-viewer/playwright.config.ts b/report-viewer/playwright.config.ts index e0eebf1a5..be79ec39e 100644 --- a/report-viewer/playwright.config.ts +++ b/report-viewer/playwright.config.ts @@ -19,7 +19,10 @@ const config: PlaywrightTestConfig = { * Maximum time expect() should wait for the condition to be met. * For example in `await expect(locator).toHaveText();` */ - timeout: 5000 + timeout: 5000, + toMatchSnapshot: { + maxDiffPixelRatio: 0.01 + } }, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, @@ -34,13 +37,13 @@ const config: PlaywrightTestConfig = { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ actionTimeout: 0, /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:5173', + baseURL: 'http://localhost:8080', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', /* Only on CI systems run the tests headless */ - headless: !!process.env.CI + headless: true }, /* Configure projects for major browsers */ @@ -103,8 +106,8 @@ const config: PlaywrightTestConfig = { * Use the preview server on CI for more realistic testing. Playwright will re-use the local server if there is already a dev-server running. */ - command: process.env.CI ? 'vite preview --port 5173' : 'vite dev', - port: 5173, + command: 'vite preview --port 8080', + port: 8080, reuseExistingServer: !process.env.CI } } diff --git a/report-viewer/tests/e2e/Cluster.spec.ts b/report-viewer/tests/e2e/Cluster.spec.ts new file mode 100644 index 000000000..62e6f516f --- /dev/null +++ b/report-viewer/tests/e2e/Cluster.spec.ts @@ -0,0 +1,51 @@ +import { test, expect } from '@playwright/test' +import { uploadFile } from './TestUtils' + +test('Test cluster view', async ({ page }) => { + await page.goto('/') + + await uploadFile('result_small_cluster.zip', page) + + // check for all clusters being shown + expect(await page.getByText('submissions in cluster').all()).toHaveLength(6) + + await page.getByText('4 94.75%').first().click() + await page.waitForURL(/\/cluster\/.*/) + + // check that the cluster graph exist + expect(page.locator('canvas').first()).not.toBeHidden() + + // switch to cluster chart + await page.getByText('Radar').first().click() + + // Check cluster diagram + await page.waitForTimeout(3000) + const radarChart = page.locator('canvas').first() + expect(page.getByRole('combobox').first()).toHaveValue('C') + const clusterImageC = radarChart.screenshot() + await page.getByRole('combobox').selectOption('B') + expect(page.getByRole('combobox').first()).toHaveValue('B') + expect(await radarChart.screenshot()).not.toEqual(clusterImageC) + + // Check comparison table + const comparisonTable = await page.textContent('body') + compareTableRow(comparisonTable, 1, 'C', 'A', 99.6, 99.6) + compareTableRow(comparisonTable, 2, 'D', 'C', 76.06, 95.93) + compareTableRow(comparisonTable, 3, 'D', 'A', 76.06, 95.93) + compareTableRow(comparisonTable, 4, 'B', 'D', 28.32, 80.85) + compareTableRow(comparisonTable, 5, 'B', 'C', 23.78, 97.16) + compareTableRow(comparisonTable, 6, 'B', 'A', 23.78, 97.16) +}) + +function compareTableRow( + table: string, + row: number, + id1: string, + id2: string, + similarityAVG: number, + similarityMAX: number +) { + expect(table).toContain( + `${row}${id1}${id2}${similarityAVG.toFixed(2)}% ${similarityMAX.toFixed(2)}%` + ) +} diff --git a/report-viewer/tests/e2e/Comparison.spec.ts b/report-viewer/tests/e2e/Comparison.spec.ts new file mode 100644 index 000000000..3873473e4 --- /dev/null +++ b/report-viewer/tests/e2e/Comparison.spec.ts @@ -0,0 +1,73 @@ +import { test, expect, Page } from '@playwright/test' +import { uploadFile } from './TestUtils' + +test('Test comparison table and comparsion view', async ({ page }) => { + await page.goto('/') + + await uploadFile('result_small_cluster.zip', page) + + const comparisonContainer = page.getByText( + 'Top Comparisons: Type in the name of a submission to only show comparisons that contain this submission. Fully written out names get unhidden.Hide AllSort By' + ) + + // check for elements in average similarity table + const comparisonTableAverageSorted = await page.getByText('Cluster1').textContent() + expect(comparisonTableAverageSorted).toContain('1CA') + expect(comparisonTableAverageSorted).toContain('2DC') + + await comparisonContainer.getByText('Maximum Similarity', { exact: true }).click() + // check for elements in maximum similarity table + const comparisonTableMaxSorted = await page.getByText('Cluster1').textContent() + expect(comparisonTableMaxSorted).toContain('1CA') + expect(comparisonTableMaxSorted).toContain('2BC') + + await page.getByText('Hide All').click() + // check for elements being hidden + const comparisonTableOverviewHidden = await page.getByText('Cluster1').textContent() + expect(comparisonTableOverviewHidden).toMatch(/1anon[0-9]+anon[0-9]+/) + expect(comparisonTableOverviewHidden).toMatch(/3anon[0-9]+anon[0-9]+/) + expect(comparisonTableOverviewHidden).toMatch(/4anon[0-9]+anon[0-9]+/) + + await page.getByPlaceholder('Filter/Unhide Comparisons').fill('A') + // check for elements being unhidden and filtered + const comparisonTableOverviewFilteredA = await page.getByText('Cluster1').textContent() + expect(comparisonTableOverviewFilteredA).toMatch(/1anon[0-9]+A/) //toContain('1HiddenA') + expect(comparisonTableOverviewFilteredA).toMatch(/3anon[0-9]+A/) + // we cant check for 4Hidden because the dynamic scroller just moves it of screen, so the text is still there but not visible + + await page.getByPlaceholder('Filter/Unhide Comparisons').fill('A C') + // check for elements being unhidden and filtered + const comparisonTableOverviewFilteredAC = await page.getByText('Cluster1').textContent() + expect(comparisonTableOverviewFilteredAC).toContain('1CA') + expect(comparisonTableOverviewFilteredAC).toMatch(/3anon[0-9]+A/) + expect(comparisonTableOverviewFilteredAC).toMatch(/4anon[0-9]+C/) + + // go to comparison page + await page.getByText('1C').click() + await page.waitForURL(/\/comparison\/.*/) + + // check for elements in comparison page + const bodyComparison = await page.locator('body').textContent() + expect(bodyComparison).toContain('Average Similarity: 99.59%') + expect(bodyComparison).toContain('GSTiling.java - GSTiling.java: 308') + expect(bodyComparison).toContain('Matches.java - Matches.java: 58') + expect(bodyComparison).toContain('A/Match.java') + expect(bodyComparison).toContain('C/Match.java') + + // check for being able to hide and unhide elements + expect(await isCodeVisible(page, 'public class Match {')).toBe(false) + await page.getByText('A/Match.java').click() + expect(await isCodeVisible(page, 'public class Match {')).toBe(true) + await page.getByText('A/Match.java').click() + expect(await isCodeVisible(page, 'public class Match {')).toBe(false) + + // unhide elements by clicking match list + expect(await isCodeVisible(page, 'public class GSTiling')).toBe(false) + await page.getByText('GSTiling.java - GSTiling.java: 308').click() + await page.waitForTimeout(1000) + expect(await isCodeVisible(page, 'public class GSTiling')).toBe(true) +}) + +async function isCodeVisible(page: Page, codePart: string) { + return await page.locator('pre', { hasText: codePart }).first().isVisible() +} diff --git a/report-viewer/tests/e2e/Distribution.spec.ts b/report-viewer/tests/e2e/Distribution.spec.ts new file mode 100644 index 000000000..5ac24191d --- /dev/null +++ b/report-viewer/tests/e2e/Distribution.spec.ts @@ -0,0 +1,52 @@ +import { test, expect, Page } from '@playwright/test' +import { uploadFile } from './TestUtils' + +test('Test distribution diagram', async ({ page }) => { + await page.goto('/') + + await uploadFile('result_small_cluster.zip', page) + + const options = getTestCombinations() + selectOptions(page, options[0]) + const canvas = page.locator('canvas').first() + let lastImage = await canvas.screenshot() + for (const option of options.slice(1)) { + await selectOptions(page, option) + const newImage = await canvas.screenshot() + expect(newImage).not.toEqual(lastImage) + lastImage = newImage + } +}) + +/** + * Checks if the distribution diagram is correct for the given options + * @param page Page currently tested on + * @param options Options to be selected + */ +async function selectOptions(page: Page, options: string[]) { + const distributionDiagramContainer = page.getByText('Distribution of Comparisons:Options:') + for (const option of options) { + await distributionDiagramContainer.getByText(option).first().click() + } + // This timeout is so that the screenshot is taken after the animation is finished + await page.waitForTimeout(3000) +} + +function getTestCombinations() { + const options = [ + ['Average', 'Maximum'], + ['Linear', 'Logarithmic'] + ] + + const combinations: string[][] = [] + + const baseOptions = options.map((o) => o[0]) + for (let i = 0; i < options.length; i++) { + for (let j = 0; j < options[i].length; j++) { + const combination = Array.from(baseOptions) + combination[i] = options[i][j] + combinations.push(combination) + } + } + return combinations +} diff --git a/report-viewer/tests/e2e/Information.spec.ts b/report-viewer/tests/e2e/Information.spec.ts new file mode 100644 index 000000000..5b6814ce5 --- /dev/null +++ b/report-viewer/tests/e2e/Information.spec.ts @@ -0,0 +1,34 @@ +import { expect, test } from '@playwright/test' +import { uploadFile } from './TestUtils' + +test('Test information page', async ({ page }) => { + await page.goto('/') + await uploadFile('result_small_cluster.zip', page) + + // check displayed information on overview page + const bodyOverview = await page.locator('body').textContent() + expect(bodyOverview).toContain('Directory: files') + expect(bodyOverview).toContain('Total Submissions: 4') + expect(bodyOverview).toContain('Total Comparisons: 6') + expect(bodyOverview).toContain('Min Token Match: 9') + + // go to information page + await page.getByText('More', { exact: true }).click() + await page.waitForURL('/info') + + // check displayed run options on information page + const runOptions = await page.getByText('Run Options:Submission Directory:').textContent() + expect(runOptions).toContain('Submission Directory: files') + expect(runOptions).toContain('Basecode Directory:') + expect(runOptions).toContain('Language: Javac based AST plugin') + expect(runOptions).toContain('File Extentions: .java, .JAVA') + expect(runOptions).toContain('Min Token Match: 9') + + const runData = await page.getByText('Run Data:Date of Execution:').textContent() + expect(runData).toContain('Date of Execution: 02/09/23') + expect(runData).toContain('Execution Duration: 12 ms') + expect(runData).toContain('Total Submissions: 4') + expect(runData).toContain('Total Comparisons: 6') + expect(runData).toContain('Shown Comparisons: 6') + expect(runData).toContain('Missing Comparisons: 0') +}) diff --git a/report-viewer/tests/e2e/TestUtils.ts b/report-viewer/tests/e2e/TestUtils.ts new file mode 100644 index 000000000..55a74b755 --- /dev/null +++ b/report-viewer/tests/e2e/TestUtils.ts @@ -0,0 +1,19 @@ +import { Page, expect } from '@playwright/test' + +/** + * Selects a file in the file chooser and uploads it. + * Expects the file to be in the tests/e2e/assets folder. + * Expects to be on the file upload page. + * @param fileName + */ +export async function uploadFile(fileName: string, page: Page) { + expect(page).toHaveURL('/') + + // upload file through file chooser + const fileChooserPromise = page.waitForEvent('filechooser') + await page.getByText('Drag and Drop zip/Json file on this page').click() + const fileChooser = await fileChooserPromise + await fileChooser.setFiles(`tests/e2e/assets/${fileName}`) + + await page.waitForURL('/overview') +} diff --git a/report-viewer/tests/e2e/assets/result_small_cluster.zip b/report-viewer/tests/e2e/assets/result_small_cluster.zip new file mode 100644 index 000000000..1ef1bdbaa Binary files /dev/null and b/report-viewer/tests/e2e/assets/result_small_cluster.zip differ diff --git a/report-viewer/tests/e2e/boilerplate.spec.ts b/report-viewer/tests/e2e/boilerplate.spec.ts deleted file mode 100644 index 556c07088..000000000 --- a/report-viewer/tests/e2e/boilerplate.spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { test } from '@playwright/test' - -test('example', async ({ page }) => { - await page.goto('/') -})