Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SWC-6555 #5200

Merged
merged 16 commits into from
Oct 16, 2023
Merged

SWC-6555 #5200

merged 16 commits into from
Oct 16, 2023

Conversation

hallieswan
Copy link
Contributor

  • add end-to-end test for File sharing
    • certify test users by default
    • add methods for uploading/deleting a file and creating/deleting projects
  • bump playwright version
  • update CI workflow to build SWC once, then shard tests per browser across different machines

Comment on lines +11 to +22
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build SWC
uses: ./.github/workflows/build
- name: Upload build to GitHub Actions Artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ env.BUILD_ARTIFACT_NAME }}
path: '${{ env.BUILD_DIR }}/${{ env.BUILD_NAME }}'
retention-days: 1
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build SWC once, then upload WAR as an artifact

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specify the full file path, so that other files in the target directory are not included in the artifact

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unique per-workflow, right? i.e. if you have 2 PRs running this job, could you have this race condition?

  1. Job 1 builds + uploads WAR artifact 1
  2. Job 2 builds + uploads WAR artifact 2
  3. Job 1 shard(s) downloads the artifact, which ends up incorrectly being WAR artifact 2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question! I think artifacts are unique per workflow run (e.g. each e2e workflow run has its own 'playwright-report' artifact), but I couldn't find something in the docs that explicitly states this.

Either way, I don't think we'd run into that race condition because per the GH docs, "You can only download artifacts that were uploaded during the same workflow run." So the Job 1 shard should only be able to download WAR artifact 1, even if another WAR artifact exists.

Comment on lines +28 to +31
strategy:
fail-fast: false
matrix:
shard: [1/3, 2/3, 3/3]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shard e2e tests for each browser across machines. Set fail-fast to false, so that all shards will finish running tests, even if tests on one shard fail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, is it smart enough to shard by browser engine?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! I don't see their sharding strategy explicitly defined in the docs, but from my tests, sharding is done by test, where tests are ordered by the project declaration order in the config file, then the alphabetized file name, then the test declaration order within each file. The tests are then distributed as evenly as possible across shards. In a simple example, running three projects (10 chromium tests, 10 firefox tests, and 5 webkit tests) on three shards results in shard 1 running 9 tests (all chromium), shard 2 running 8 tests (1 chromium, 7 firefox), and shard 3 running 8 tests (3 firefox, 5 webkit).

Since we have one project per browser that runs all tests, using an equal number of shards as browsers ensures that each shard only runs tests for one browser. However, if we start writing browser-specific tests or change how the projects are defined, we may need a more complicated strategy to ensure that each shard runs one browser (or decide whether that's necessary).

I've seen issues requesting other sharding strategies (e.g. sharding by test time), so it's also possible that more sophisticated strategies will be available in the future.

Comment on lines -17 to -23
# workaround for https://github.com/actions/runner-images/issues/8104
# ...and handle 8.1.0 regression: https://github.com/Homebrew/homebrew-core/issues/140244
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
brew uninstall --ignore-dependencies qemu
curl -OSL https://raw.githubusercontent.com/Homebrew/homebrew-core/dc0669eca9479e9eeb495397ba3a7480aaa45c2e/Formula/qemu.rb
brew install ./qemu.rb

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workaround no longer needed -- qemu has been updated in the GitHub runners

run: yarn playwright test
- name: Upload Playwright Report
run: yarn playwright test --shard ${{ matrix.shard }}
- name: Upload blob report to GitHub Actions Artifacts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each shard will produce its own blob report, so upload these as an artifact

Comment on lines +92 to +98
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v3
with:
name: ${{ env.PW_ALL_BLOBS_DIR }}
path: ${{ env.PW_ALL_BLOBS_DIR }}
- name: Merge into HTML Report
run: yarn playwright merge-reports --reporter html ./"${{ env.PW_ALL_BLOBS_DIR }}"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combine the blob reports created by each shard into a single HTML report

Comment on lines +102 to +114
const userContext = await browser.newContext({
storageState: storageStatePaths['swc-e2e-user'],
})
const userPage = await userContext.newPage()
const userAccessToken = await getAccessTokenFromCookie(userPage)

// create project
const userProjectName = generateEntityName('project')
const userProjectId = await createProject(
userProjectName,
userAccessToken,
userPage,
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create the Project with the test user's access token so that the project is initially only accessible by the test user

Comment on lines +141 to +149
if (browserName === 'webkit') {
test.info().annotations.push({
type: 'very slow',
description: `webkit in CI only. May be related to the InviteWidget.
Evaluate if timeout can be removed after addressing
https://sagebionetworks.jira.com/browse/SWC-6569.`,
})
test.setTimeout(testInfo.timeout * 5)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The files test is intermittently very slow in webkit in CI (5-7 minutes in CI vs 35 seconds locally), but does eventually pass. Increase overall test timeout and timeout of particularly slow expectations to help this test pass when it is slow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's really weird. This annotations system is cool though, so thanks for adding all of this potentially helpful information.

name: 'Enter name...',
})
await userSuggestBox.fill(validatedUserName)
await userSuggestBox.press('Shift')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

press and pressSequentially are particularly slow in webkit in CI, so fill the validated username to avoid calling press for each letter of the validated user's name, then press Shift to trigger the user suggestion dropdown, as discussed in SWC-6569

Comment on lines 50 to 52
const validatedUserPage = await context.newPage()
await use(validatedUserPage)
const userPage = await context.newPage()
await use(userPage)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General method, so use a general variable name

Comment on lines +82 to +83
await base.step(`certify test user - ${user.username}`, async () => {
await setCertifiedUserStatus(userId, true, getAdminPAT(), page)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users must be certified to upload a file -- certify new users by default, similar to backend integration tests

Comment on lines +145 to +175
// Get a FileHandle using its ID.
// Note: Only the user that created the FileHandle can access it directly.
export async function getFileHandle(
accessToken: string,
handleId: string,
page: Page,
) {
return (await doGet(
page,
`/file/v1/fileHandle/${handleId}`,
accessToken,
BackendDestinationEnum.REPO_ENDPOINT,
)) as FileHandle
}

// Delete a FileHandle using its ID.
// Note: Only the user that created the FileHandle can delete it.
// Also, a FileHandle cannot be deleted if it is associated with a FileEntity or WikiPage
export async function deleteFileHandle(
accessToken: string,
handleId: string,
page: Page,
) {
await doDelete(
page,
`/file/v1/fileHandle/${handleId}`,
accessToken,
BackendDestinationEnum.REPO_ENDPOINT,
)
return handleId
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved from e2e/helpers/messages.ts so these methods are colocated with other entity methods

Comment on lines -46 to -76
// Get a FileHandle using its ID.
// Note: Only the user that created the FileHandle can access it directly.
async function getFileHandle(
accessToken: string,
handleId: string,
page: Page,
) {
return (await doGet(
page,
`/file/v1/fileHandle/${handleId}`,
accessToken,
BackendDestinationEnum.REPO_ENDPOINT,
)) as FileHandle
}

// Delete a FileHandle using its ID.
// Note: Only the user that created the FileHandle can delete it.
// Also, a FileHandle cannot be deleted if it is associated with a FileEntity or WikiPage
async function deleteFileHandle(
accessToken: string,
handleId: string,
page: Page,
) {
await doDelete(
page,
`/file/v1/fileHandle/${handleId}`,
accessToken,
BackendDestinationEnum.REPO_ENDPOINT,
)
return handleId
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved into e2e/helpers/entities.ts

Comment on lines -87 to -89
const loadingButton = page.getByRole('button', { name: 'Logging you in' })
await expect(loadingButton).toBeVisible()
await expect(loadingButton).not.toBeVisible()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This state is very short-lived, so was intermittently missed and caused tests to fail, even when wrapped in a Promise.all. This expectation was originally added to help us understand why login intermittently failed, but removing it since actually caused tests to be flakier and the check for the URL and the username/password alert should help us evaluate, if needed

"e2e:report": "yarn playwright show-report"
"e2e:report": "yarn playwright show-report",
"e2e:report:blob": "yarn playwright merge-reports --reporter html ./blob-report && yarn e2e:report",
"playwright:update": "yarn add -D @playwright/test@latest; yarn playwright install"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update Playwright browsers after updating the Playwright version

@@ -32,7 +32,7 @@ export default defineConfig({
workers: process.env.CI ? 1 : 2,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
// Concise 'dot' for CI, default 'list' when running locally
reporter: process.env.CI ? [['list'], ['html']] : 'html',
reporter: process.env.CI ? [['list'], ['blob']] : 'html',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use 'blob' format in CI, so that the report from each shard can be merged into a single HTML report

@hallieswan hallieswan marked this pull request as ready for review October 13, 2023 16:50
@hallieswan hallieswan requested a review from nickgros October 13, 2023 16:50
@hallieswan hallieswan self-assigned this Oct 13, 2023
Copy link
Contributor

@nickgros nickgros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, though I had a few questions

Comment on lines +11 to +22
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build SWC
uses: ./.github/workflows/build
- name: Upload build to GitHub Actions Artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ env.BUILD_ARTIFACT_NAME }}
path: '${{ env.BUILD_DIR }}/${{ env.BUILD_NAME }}'
retention-days: 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unique per-workflow, right? i.e. if you have 2 PRs running this job, could you have this race condition?

  1. Job 1 builds + uploads WAR artifact 1
  2. Job 2 builds + uploads WAR artifact 2
  3. Job 1 shard(s) downloads the artifact, which ends up incorrectly being WAR artifact 2

Comment on lines +28 to +31
strategy:
fail-fast: false
matrix:
shard: [1/3, 2/3, 3/3]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, is it smart enough to shard by browser engine?

Comment on lines +141 to +149
if (browserName === 'webkit') {
test.info().annotations.push({
type: 'very slow',
description: `webkit in CI only. May be related to the InviteWidget.
Evaluate if timeout can be removed after addressing
https://sagebionetworks.jira.com/browse/SWC-6569.`,
})
test.setTimeout(testInfo.timeout * 5)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's really weird. This annotations system is cool though, so thanks for adding all of this potentially helpful information.

Comment on lines 227 to 229
// Cookies banner covers the button to save settings
// ...so close banner by accepting cookies
await acceptSiteCookies(userPage)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to move this into a fixture, and just auto-accept for every new page (user?) instance? I don't think we should have to think about this in this test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll add it in to the user page fixtures!


await fileLink.click()
await expect(
userPage.getByText(`Discussion about ${fileName}`),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, why look for "Discussion about" ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good question -- this was an easy locator to confirm that the File page had loaded, but I'll add some other expectations about the File page for completeness!

})
},
)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test suite is exceptionally readable!!

@hallieswan hallieswan merged commit 2eae71c into Sage-Bionetworks:develop Oct 16, 2023
@hallieswan hallieswan deleted the SWC-6555 branch October 16, 2023 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants