-
Notifications
You must be signed in to change notification settings - Fork 39
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
SWC-6555 #5200
Conversation
hallieswan
commented
Oct 13, 2023
- 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
…es, so deletion order follows backend integration tests and multiple projects/files can be created per test, add e2e test for file sharing workflow, bump playwright version
…hort-lived saving button
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
- Job 1 builds + uploads WAR artifact 1
- Job 2 builds + uploads WAR artifact 2
- Job 1 shard(s) downloads the artifact, which ends up incorrectly being WAR artifact 2
There was a problem hiding this comment.
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.
strategy: | ||
fail-fast: false | ||
matrix: | ||
shard: [1/3, 2/3, 3/3] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
# 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 | ||
|
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
- 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 }}" |
There was a problem hiding this comment.
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
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, | ||
) |
There was a problem hiding this comment.
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
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) | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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
const validatedUserPage = await context.newPage() | ||
await use(validatedUserPage) | ||
const userPage = await context.newPage() | ||
await use(userPage) |
There was a problem hiding this comment.
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
await base.step(`certify test user - ${user.username}`, async () => { | ||
await setCertifiedUserStatus(userId, true, getAdminPAT(), page) |
There was a problem hiding this comment.
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
// 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 | ||
} |
There was a problem hiding this comment.
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
// 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 | ||
} |
There was a problem hiding this comment.
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
const loadingButton = page.getByRole('button', { name: 'Logging you in' }) | ||
await expect(loadingButton).toBeVisible() | ||
await expect(loadingButton).not.toBeVisible() |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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
There was a problem hiding this 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
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 |
There was a problem hiding this comment.
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?
- Job 1 builds + uploads WAR artifact 1
- Job 2 builds + uploads WAR artifact 2
- Job 1 shard(s) downloads the artifact, which ends up incorrectly being WAR artifact 2
strategy: | ||
fail-fast: false | ||
matrix: | ||
shard: [1/3, 2/3, 3/3] |
There was a problem hiding this comment.
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?
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) | ||
} |
There was a problem hiding this comment.
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.
e2e/files.spec.ts
Outdated
// Cookies banner covers the button to save settings | ||
// ...so close banner by accepting cookies | ||
await acceptSiteCookies(userPage) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
e2e/files.spec.ts
Outdated
|
||
await fileLink.click() | ||
await expect( | ||
userPage.getByText(`Discussion about ${fileName}`), |
There was a problem hiding this comment.
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" ?
There was a problem hiding this comment.
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!
}) | ||
}, | ||
) | ||
}) |
There was a problem hiding this comment.
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!!
…n user page fixture, per PR