-
Notifications
You must be signed in to change notification settings - Fork 5k
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
chore: validate page object usage in new spec files on every PR #29915
base: main
Are you sure you want to change the base?
Changes from 2 commits
5ea6f0f
d11f3c4
0383d86
7c39992
c209015
451fd5d
b450b65
04c7dd4
8a9f7c1
ccabc5e
62f8058
575b28d
c8d27ab
8910bc8
725fab7
de4975b
da7acf5
9376bf7
0b58c0c
c501046
2adb681
a65e2eb
ba02294
28a43d2
d96ac91
4702302
684366c
3f58395
41427bd
d49bd8b
9e15a13
0792a3b
3bd0661
0642693
fdea90e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
import { fetchManifestFlagsFromPRAndGit } from '../../development/lib/get-manifest-flag'; | ||
import { filterE2eChangedFiles } from '../../test/e2e/changedFilesUtil'; | ||
import { filterE2eChangedFiles, readChangedFiles } from '../../test/e2e/changedFilesUtil'; | ||
|
||
fetchManifestFlagsFromPRAndGit().then((manifestFlags) => { | ||
let timeout; | ||
|
||
if (manifestFlags.circleci?.timeoutMinutes) { | ||
timeout = manifestFlags.circleci?.timeoutMinutes; | ||
} else { | ||
const changedOrNewTests = filterE2eChangedFiles(); | ||
const changedFiles = readChangedFiles(); | ||
const changedOrNewTests = filterE2eChangedFiles(changedFiles); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is due to a change in the filterE2eChangedFiles as now it accepts the files in the func argument, so it can also be re-used in the github action job |
||
|
||
// 20 minutes, plus 3 minutes for every changed file, up to a maximum of 30 minutes | ||
timeout = Math.min(20 + changedOrNewTests.length * 3, 30); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { execSync } from 'child_process'; | ||
import fs from 'fs'; | ||
|
||
const OWNER = 'MetaMask'; | ||
const REPOSITORY = 'metamask-extension'; | ||
|
||
/** | ||
* Downloads an artifact from CircleCI. | ||
* @param branch - The branch name. | ||
* @param headCommitHash - The commit hash of the branch. | ||
* @param artifactName - The name of the artifact to download. | ||
* @param outputFilePath - The path to save the downloaded artifact. | ||
* @param jobName - The name of the job that produced the artifact. | ||
*/ | ||
export function downloadCircleCiArtifact(branch: string, headCommitHash: string, artifactName: string, outputFilePath: string, jobName: string): void { | ||
// Get the pipeline ID for the current branch | ||
const pipelineId = execSync( | ||
`curl --silent "https://circleci.com/api/v2/project/gh/${OWNER}/${REPOSITORY}/pipeline?branch=${branch}" | jq --arg head_commit_hash "${headCommitHash}" -r '.items | map(select(.vcs.revision == $head_commit_hash)) | first | .id'` | ||
|
||
).toString().trim(); | ||
|
||
// Get the workflow ID for the pipeline | ||
const workflowId = execSync( | ||
`curl --silent "https://circleci.com/api/v2/pipeline/${pipelineId}/workflow" | jq -r '.items[0].id'` | ||
).toString().trim(); | ||
|
||
// Get the job details for the specific job that produces artifacts | ||
const jobDetails = execSync( | ||
`curl --silent "https://circleci.com/api/v2/workflow/${workflowId}/job" | jq --arg job_name "${jobName}" -r '.items[] | select(.name == $job_name)'` | ||
).toString(); | ||
|
||
const jobId = JSON.parse(jobDetails).id; | ||
|
||
// Get the artifact URL | ||
const artifactList = execSync( | ||
`curl --silent "https://circleci.com/api/v2/project/gh/${OWNER}/${REPOSITORY}/${jobId}/artifacts"` | ||
).toString(); | ||
const artifact = JSON.parse(artifactList).items.find((item: any) => item.path.includes(artifactName)); | ||
|
||
if (!artifact) { | ||
throw new Error(`Artifact ${artifactName} not found`); | ||
} | ||
|
||
const artifactUrl = artifact.url; | ||
|
||
// Download the artifact | ||
execSync(`curl --silent --location "${artifactUrl}" --output "${outputFilePath}"`); | ||
|
||
if (!fs.existsSync(outputFilePath)) { | ||
throw new Error(`Failed to download artifact to ${outputFilePath}`); | ||
} | ||
} | ||
|
||
/** | ||
* Reads the content of an artifact file. | ||
* @param filePath - The path to the downloaded artifact. | ||
* @returns The content of the artifact file. | ||
*/ | ||
export function readArtifact(filePath: string): string { | ||
if (!fs.existsSync(filePath)) { | ||
throw new Error(`File not found: ${filePath}`); | ||
} | ||
|
||
const content = fs.readFileSync(filePath, 'utf-8').trim(); | ||
return content; | ||
} | ||
|
||
/** | ||
* Sleep function to pause execution for a specified number of seconds. | ||
* @param seconds - The number of seconds to sleep. | ||
*/ | ||
export function sleep(seconds: number) { | ||
return new Promise(resolve => setTimeout(resolve, seconds * 1000)); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import fs from 'fs'; | ||
import { execSync } from 'child_process'; | ||
import { filterE2eChangedFiles } from '../../../test/e2e/changedFilesUtil'; | ||
import { downloadCircleCiArtifact, readArtifact, sleep } from './utils'; | ||
|
||
async function verifyE2ePageObjectsUsage() { | ||
let e2eFiles: string[]; | ||
|
||
if (process.env.GITHUB_ACTIONS) { | ||
// Running in Github Actions | ||
const branch = process.env.GITHUB_REF_NAME || ''; | ||
const headCommitHash = process.env.GITHUB_SHA || ''; | ||
const artifactName = 'changed-files.txt'; | ||
const artifactPath = 'changed-files'; | ||
const jobName = 'get-changed-files-with-git-diff'; // Specify the job name | ||
|
||
let attempts = 0; | ||
const maxAttempts = 3; | ||
let changedFilesContent = ''; | ||
|
||
while (attempts < maxAttempts) { | ||
try { | ||
console.log(`Downloading artifact: Attempt ${attempts + 1}/${maxAttempts}`); | ||
const outputDir = `${artifactPath}/changed-files.txt`; | ||
downloadCircleCiArtifact(branch, headCommitHash, artifactName, outputDir, jobName); // Pass the job name | ||
|
||
changedFilesContent = readArtifact(outputDir); | ||
|
||
if (changedFilesContent) { | ||
console.log('Artifact downloaded and read successfully.'); | ||
break; | ||
} | ||
} catch (error) { | ||
console.error(`Error fetching artifact: ${error.message}`); | ||
} | ||
|
||
attempts++; | ||
if (attempts < maxAttempts) { | ||
console.log(`Retrying in 60 seconds... (${attempts}/${maxAttempts})`); | ||
await sleep(60000); // Wait for 60 seconds before retrying | ||
} | ||
} | ||
|
||
if (!changedFilesContent) { | ||
console.error('No artifacts found for changed files. Exiting with failure.'); | ||
process.exit(1); | ||
} | ||
|
||
// Use the filterE2eChangedFiles function to filter E2E files | ||
e2eFiles = filterE2eChangedFiles(changedFilesContent.split('\n').filter(file => file.trim() !== '')); | ||
console.log('e2e changed files', e2eFiles); | ||
} else { | ||
// Running locally | ||
console.log('Running locally, performing git diff...'); | ||
const diffOutput = execSync('git diff --name-only HEAD').toString().trim(); | ||
const changedFiles = diffOutput.split('\n').filter(file => file.trim() !== ''); | ||
console.log('Changed files:', changedFiles); | ||
|
||
e2eFiles = filterE2eChangedFiles(changedFiles); | ||
console.log('Filtered E2E files:', e2eFiles); | ||
} | ||
|
||
if (e2eFiles.length === 0) { | ||
console.log('No E2E files to validate. Exiting successfully.'); | ||
process.exit(0); | ||
} | ||
|
||
// Check each E2E file for page object usage | ||
for (const file of e2eFiles) { | ||
const content = fs.readFileSync(file, 'utf8'); | ||
// Check for the presence of page object imports | ||
const usesPageObjectModel = content.includes("from './page-objects/") || | ||
content.includes("import") && content.includes("from '../../page-objects/"); | ||
|
||
if (!usesPageObjectModel) { | ||
console.error(`\x1b[31mFailure: You need to use Page Object Model in ${file}\x1b[0m`); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
console.log("\x1b[32mSuccess: All the new or modified E2E files use the Page Object Model.\x1b[0m"); | ||
} | ||
|
||
// Run the verification | ||
verifyE2ePageObjectsUsage().catch((error) => { | ||
console.error('Not all the modified e2e use the Page Object Model', error); | ||
process.exit(1); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
name: Validate E2E Page Object usage on modified files | ||
|
||
on: | ||
pull_request: | ||
types: [opened, synchronize] | ||
|
||
jobs: | ||
run-e2e-validation: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v4 | ||
|
||
- name: Setup environment | ||
uses: metamask/github-tools/.github/actions/setup-environment@main | ||
|
||
- name: Run E2E Page Object Validation | ||
seaona marked this conversation as resolved.
Show resolved
Hide resolved
|
||
run: | | ||
yarn validate-e2e-page-object-usage |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're up for it, this looks like a great PR to migrate this file to TS. If you get into some really sticky situation, don't worry about it, but I think it might be straightforward. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👋 @HowardBraham thanks for the comment! So I changed it to TS but then I fond that we also need to change the run-all.js and possibly a couple of more files? So I think we could leave it out of scope of this PR, but let me know if you think otherwise 🙏 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I was slightly scared of that, okay leave it alone 🙂 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ const CHANGED_FILES_PATH = path.join( | |
/** | ||
* Reads the list of changed files from the git diff file. | ||
* | ||
* @returns {<string[]>} An array of changed file paths. | ||
* @returns {string[]} An array of changed file paths. | ||
*/ | ||
function readChangedFiles() { | ||
try { | ||
|
@@ -29,10 +29,10 @@ function readChangedFiles() { | |
/** | ||
* Filters the list of changed files to include only E2E test files within the 'test/e2e/' directory. | ||
* | ||
* @returns {<string[]>} An array of filtered E2E test file paths. | ||
* @param {string[]} changedFiles - An array of changed file paths to filter. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: can we rename to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make sense, thank you!! |
||
* @returns {string[]} An array of filtered E2E test file paths. | ||
*/ | ||
function filterE2eChangedFiles() { | ||
const changedFiles = readChangedFiles(); | ||
function filterE2eChangedFiles(changedFiles) { | ||
const e2eChangedFiles = changedFiles | ||
.filter( | ||
(file) => | ||
|
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.
before, we were just reading from the files, as the jobs that needed this were all in circle ci.
Now we need to store the results as an artifact, because we need to access the result from github actions too.