diff --git a/.github/workflows/complete-e2e.yml b/.github/workflows/complete-e2e.yml new file mode 100644 index 000000000..d27eb0018 --- /dev/null +++ b/.github/workflows/complete-e2e.yml @@ -0,0 +1,157 @@ +# Builds JPlag and then runs Datasets and Report Viewer e2e tests on multiple OS +name: Complete e2e Test + +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened] + paths: + - ".github/workflows/complete-e2e.yml" + - "report-viewer/**" + - "**/pom.xml" + - "**.java" + - "**.g4" + +jobs: + pre_job: + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + steps: + - id: skip_check + uses: fkirc/skip-duplicate-actions@master + with: + concurrent_skipping: 'same_content_newer' + skip_after_successful_duplicate: 'true' + + build_jar: + needs: pre_job + if: ${{ needs.pre_job.outputs.should_skip != 'true' }} + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: 'temurin' + + - uses: actions/setup-node@v4 + with: + node-version: "18" + + - name: Build Assembly + run: mvn -Pwith-report-viewer -DskipTests clean package assembly:single + + - name: Rename Jar + run: mv cli/target/jplag-*-jar-with-dependencies.jar cli/target/jplag.jar + + - name: Upload Assembly + uses: actions/upload-artifact@v4 + with: + name: "JPlag" + path: "cli/target/jplag.jar" + retention-days: 30 + + run_jplag: + needs: build_jar + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + dataset: [ + {zip: "progpedia.zip", name: "progpedia", folder: "ACCEPTED", language: "java", cliArgs: "-bc base"}, + {zip: "fileSingleRoot.zip", name: "fileSingleRoot", folder: "fileSingleRoot", language: "java", cliArgs: ""}, + {zip: "folderSingleRoot.zip", name: "folderSingleRoot", folder: "folderSingleRoot", language: "java", cliArgs: ""}, + {zip: "fileMultiRoot.zip", name: "fileMultiRoot", folder: "f0", language: "java", cliArgs: "--new f1"}, + {zip: "folderMultiRoot.zip", name: "folderMultiRoot", folder: "f0", language: "java", cliArgs: "--new f1"}, + {zip: "mixedMultiRoot.zip", name: "mixedBaseFile", folder: "f0", language: "java", cliArgs: "--new f1"}, + {zip: "mixedMultiRoot.zip", name: "mixedBaseFolder", folder: "f1", language: "java", cliArgs: "--new f0"} + ] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: 'temurin' + + - name: Get JAR + uses: actions/download-artifact@v4 + with: + name: JPlag + + - name: Copy and unzip dataset windows + if: ${{ matrix.os == 'windows-latest' }} + run: | + Expand-Archive -LiteralPath .github/workflows/files/${{ matrix.dataset.zip }} -DestinationPath ./ + + - name: Copy and unzip dataset macos and ubuntu + if: ${{ matrix.os == 'macos-latest' || matrix.os == 'ubuntu-latest'}} + run: | + unzip .github/workflows/files/${{ matrix.dataset.zip }} + + - name: Run JPlag + run: | + java -jar jplag.jar ${{ matrix.dataset.folder }} -l ${{ matrix.dataset.language }} -r ${{ matrix.dataset.name }}-report ${{ matrix.dataset.cliArgs }} + + - name: Upload result + uses: actions/upload-artifact@v4 + with: + name: "${{ matrix.dataset.name }}-${{ matrix.os }}" + path: "${{ matrix.dataset.name }}-report.zip" + retention-days: 30 + + e2e_test: + needs: run_jplag + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "18" + + - name: Install and Build + working-directory: report-viewer + run: | + npm install + npm run build + + - name: Install playwright + working-directory: report-viewer + run: npx playwright install --with-deps + + - name: Download JPlag Reports + uses: actions/download-artifact@v4 + with: + pattern: "*-${{ matrix.os }}" + path: "report-viewer/tests/e2e/assets" + merge-multiple: true + + - name: Run tests + working-directory: report-viewer + run: | + npm run test:e2e + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: "test-results-${{ matrix.os }}" + path: | + report-viewer/test-results + report-viewer/playwright-report + retention-days: 30 \ No newline at end of file diff --git a/.github/workflows/files/fileMultiRoot.zip b/.github/workflows/files/fileMultiRoot.zip new file mode 100644 index 000000000..24baa4dbf Binary files /dev/null and b/.github/workflows/files/fileMultiRoot.zip differ diff --git a/.github/workflows/files/fileSingleRoot.zip b/.github/workflows/files/fileSingleRoot.zip new file mode 100644 index 000000000..778ceace2 Binary files /dev/null and b/.github/workflows/files/fileSingleRoot.zip differ diff --git a/.github/workflows/files/folderMultiRoot.zip b/.github/workflows/files/folderMultiRoot.zip new file mode 100644 index 000000000..db8f2f80f Binary files /dev/null and b/.github/workflows/files/folderMultiRoot.zip differ diff --git a/.github/workflows/files/folderSingleRoot.zip b/.github/workflows/files/folderSingleRoot.zip new file mode 100644 index 000000000..d23caf3d4 Binary files /dev/null and b/.github/workflows/files/folderSingleRoot.zip differ diff --git a/.github/workflows/files/mixedMultiRoot.zip b/.github/workflows/files/mixedMultiRoot.zip new file mode 100644 index 000000000..628c301e7 Binary files /dev/null and b/.github/workflows/files/mixedMultiRoot.zip differ diff --git a/.github/workflows/report-viewer-e2e.yml b/.github/workflows/report-viewer-e2e.yml deleted file mode 100644 index 98744e554..000000000 --- a/.github/workflows/report-viewer-e2e.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Report Viewer e2e Test - -on: - workflow_dispatch: - pull_request: - types: [opened, synchronize, reopened] - paths: - - ".github/workflows/report-viewer-e2e.yml" - - "report-viewer/**" - -jobs: - pre_job: - runs-on: ubuntu-latest - outputs: - should_skip: ${{ steps.skip_check.outputs.should_skip }} - steps: - - id: skip_check - uses: fkirc/skip-duplicate-actions@master - with: - concurrent_skipping: 'same_content_newer' - skip_after_successful_duplicate: 'true' - - test: - needs: pre_job - if: ${{ needs.pre_job.outputs.should_skip != 'true' }} - runs-on: ubuntu-latest - steps: - - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: "18" - - - name: Install and Build ๐Ÿ”ง - working-directory: report-viewer - run: | - npm install - 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@v4 - 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/.gitignore b/report-viewer/.gitignore index 719bf30c1..9d1252439 100644 --- a/report-viewer/.gitignore +++ b/report-viewer/.gitignore @@ -29,3 +29,4 @@ coverage test-results/ playwright-report/ +tests/e2e/assets \ No newline at end of file diff --git a/report-viewer/tests/e2e/Comparison.spec.ts b/report-viewer/tests/e2e/Comparison.spec.ts index 3873473e4..98a723715 100644 --- a/report-viewer/tests/e2e/Comparison.spec.ts +++ b/report-viewer/tests/e2e/Comparison.spec.ts @@ -4,23 +4,24 @@ import { uploadFile } from './TestUtils' test('Test comparison table and comparsion view', async ({ page }) => { await page.goto('/') - await uploadFile('result_small_cluster.zip', page) + await uploadFile('progpedia-report.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 page.getByPlaceholder('Filter/Unhide Comparisons').fill('Purple') + const comparisonTableAverageSorted = await page.getByText(/Cluster[0-9]/).textContent() + expect(comparisonTableAverageSorted).toContain('100Purple FishBeige Dog') 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.getByPlaceholder('Filter/Unhide Comparisons').fill('Blue') + const comparisonTableMaxSorted = await page.getByText(/Cluster[0-9]/).textContent() + expect(comparisonTableMaxSorted).toContain('100Blue AntelopeLime Lynx') + await page.getByPlaceholder('Filter/Unhide Comparisons').fill('') await page.getByText('Hide All').click() // check for elements being hidden const comparisonTableOverviewHidden = await page.getByText('Cluster1').textContent() @@ -28,7 +29,8 @@ test('Test comparison table and comparsion view', async ({ page }) => { 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') + // Temporarily disabled due to https://github.com/jplag/JPlag/issues/1629 + /*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') @@ -40,32 +42,43 @@ test('Test comparison table and comparsion view', async ({ page }) => { 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/) + expect(comparisonTableOverviewFilteredAC).toMatch(/4anon[0-9]+C/)+/*/ + await page.getByText('Show All').click() + await page.getByPlaceholder('Filter/Unhide Comparisons').fill('Lazy') // go to comparison page - await page.getByText('1C').click() + await page.getByText('102Gray WolfLazy Bobcat').click() await page.waitForURL(/\/comparison\/.*/) // check for elements in comparison page + const submissionName1 = 'Gray Wolf' + const submissionName2 = 'Lazy Bobcat' + const fileName1 = 'Sociologia.java' + const fileName2 = 'Daa_sociologia.java' + const content1 = 'class Aluno' + const content2 = 'class Node' + 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') + expect(bodyComparison).toMatch(/Average Similarity: [0-9]{2}.[0-9]{2}%/) + expect(bodyComparison).toMatch(new RegExp(`Similarity ${submissionName1}: [0-9]{2}.[0-9]{2}%`)) + expect(bodyComparison).toMatch(new RegExp(`Similarity ${submissionName2}: [0-9]{2}.[0-9]{2}%`)) + + expect(bodyComparison).toMatch(new RegExp(`${fileName1} - ${fileName2}: [0-9]+`)) + expect(bodyComparison).toContain(`${submissionName1}/${fileName1}`) + expect(bodyComparison).toContain(`${submissionName2}/${fileName2}`) // 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) + expect(await isCodeVisible(page, content1)).toBe(false) + await page.getByText(`${submissionName1}/${fileName1}`).click() + expect(await isCodeVisible(page, content1)).toBe(true) + await page.getByText(`${submissionName1}/${fileName1}`).click() + expect(await isCodeVisible(page, content1)).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() + expect(await isCodeVisible(page, content2)).toBe(false) + await page.getByText(`${fileName1} - ${fileName2}:`).first().click() await page.waitForTimeout(1000) - expect(await isCodeVisible(page, 'public class GSTiling')).toBe(true) + expect(await isCodeVisible(page, content2)).toBe(true) }) async function isCodeVisible(page: Page, codePart: string) { diff --git a/report-viewer/tests/e2e/Distribution.spec.ts b/report-viewer/tests/e2e/Distribution.spec.ts index 5ac24191d..17a7b5b4e 100644 --- a/report-viewer/tests/e2e/Distribution.spec.ts +++ b/report-viewer/tests/e2e/Distribution.spec.ts @@ -4,7 +4,7 @@ import { uploadFile } from './TestUtils' test('Test distribution diagram', async ({ page }) => { await page.goto('/') - await uploadFile('result_small_cluster.zip', page) + await uploadFile('progpedia-report.zip', page) const options = getTestCombinations() selectOptions(page, options[0]) diff --git a/report-viewer/tests/e2e/Information.spec.ts b/report-viewer/tests/e2e/Information.spec.ts index f9ca4043d..2ecb21fbb 100644 --- a/report-viewer/tests/e2e/Information.spec.ts +++ b/report-viewer/tests/e2e/Information.spec.ts @@ -3,32 +3,32 @@ import { uploadFile } from './TestUtils' test('Test information page', async ({ page }) => { await page.goto('/') - await uploadFile('result_small_cluster.zip', page) + await uploadFile('progpedia-report.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') + expect(bodyOverview).toContain('Directory: ') + expect(bodyOverview).toMatch(/Total Submissions: [0-9]+/) + expect(bodyOverview).toMatch(/Total Comparisons: [0-9]+/) + expect(bodyOverview).toMatch(/Min Token Match: [0-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 Extensions: .java, .JAVA') - expect(runOptions).toContain('Min Token Match: 9') + const runOptions = await page.getByText('Run Options:Language:').textContent() + expect(runOptions).toContain('Submission Directories: ') + expect(runOptions).toContain('Base Directory: ') + expect(runOptions).toContain('Language: ') + expect(runOptions).toContain('File Suffixes: ') + expect(runOptions).toMatch(/Min Token Match: [0-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') + expect(runData).toMatch(/Date of Execution: [0-9]{2}\/[0-9]{2}\/[0-9]{2}/) + expect(runData).toMatch(/Execution Duration: [0-9]+ ms/) + expect(runData).toMatch(/Total Submissions: [0-9]+/) + expect(runData).toMatch(/Total Comparisons: [0-9]+/) + expect(runData).toMatch(/Shown Comparisons: [0-9]+/) + expect(runData).toMatch(/Missing Comparisons: [0-9]+/) }) diff --git a/report-viewer/tests/e2e/OpenComparisonTest.spec.ts b/report-viewer/tests/e2e/OpenComparisonTest.spec.ts new file mode 100644 index 000000000..5a3976e7c --- /dev/null +++ b/report-viewer/tests/e2e/OpenComparisonTest.spec.ts @@ -0,0 +1,63 @@ +// Ensures that different styles of passing submissions are supported +import { expect, test } from '@playwright/test' +import { uploadFile } from './TestUtils' + +interface DataSet { + datasetName: string + firstSubmissionName: string + secondSubmissionName: string +} + +const testSets: DataSet[] = [ + { + datasetName: 'fileSingleRoot-report.zip', + firstSubmissionName: '0.java', + secondSubmissionName: '1.java' + }, + { + datasetName: 'folderSingleRoot-report.zip', + firstSubmissionName: '0', + secondSubmissionName: '1' + }, + // Disabled due to https://github.com/jplag/JPlag/issues/1610 + /*{ + datasetName: 'fileMultiRoot-report.zip', + firstSubmissionName: 'f0\\\\|/0.java', + secondSubmissionName: 'f1\\\\|/1.java' + }, + { + datasetName: 'mixedBaseFile-report.zip', + firstSubmissionName: 'f0\\\\|/0.java', + secondSubmissionName: 'f1\\\\|/1' + }, + { + datasetName: 'mixedBaseFolder-report.zip', + firstSubmissionName: 'f0\\\\|/0.java', + secondSubmissionName: 'f1\\\\|/1' + },*/ + { + datasetName: 'folderMultiRoot-report.zip', + firstSubmissionName: 'f0\\\\|/0', + secondSubmissionName: 'f1\\\\|/1' + } +] + +for (const testSet of testSets) { + test(`Can open ${testSet.datasetName}`, async ({ page }) => { + await page.goto('/') + + await uploadFile(testSet.datasetName, page) + + const comparisonTable = await page.getByText('Cluster1').textContent() + + const lineRegEx = RegExp('1' + testSet.firstSubmissionName + testSet.secondSubmissionName) + expect(comparisonTable).toMatch(lineRegEx) + await page.getByText(lineRegEx).click() + await page.waitForURL(/\/comparison\/.*/) + + const bodyComparison = await page.locator('body').textContent() + expect(bodyComparison).toMatch( + RegExp(`Comparison: ${testSet.firstSubmissionName} - ${testSet.secondSubmissionName}`) + ) + }) +}