mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
ddfa8a780b
Summary: This change improves the E2E testing by downloading the iOS RNTesterApp that is built in CI instead of building it locally. This should let us save 10 to 20 minutes when we test a new release. ## Changelog: [Internal] - Use the RNTester app built in CI for release testing on iOS Pull Request resolved: https://github.com/facebook/react-native/pull/48637 Test Plan: - build the app in ci - run `yarn test-e2e-local -c <my-token>` and `yarn test-e2e-local -h false -c <my-token>` and verify that the iOS app is not built, but run in the simulator Reviewed By: cortinico Differential Revision: D68161477 Pulled By: cipolleschi fbshipit-source-id: 577d110f9ff0197a2d3348a08a60e60a4d0a752b
265 lines
6.5 KiB
JavaScript
265 lines
6.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow strict-local
|
|
* @format
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const chalk = require('chalk');
|
|
const {execSync: exec} = require('child_process');
|
|
const fetch = require('node-fetch');
|
|
|
|
/*::
|
|
type CIHeaders = {
|
|
Authorization: string,
|
|
Accept: string,
|
|
'X-GitHub-Api-Version': string
|
|
}
|
|
|
|
type WorkflowRun = {
|
|
id: number,
|
|
name: string,
|
|
run_number: number,
|
|
status: "queued" | "in_progress" | "completed",
|
|
workflow_id: number,
|
|
url: string,
|
|
created_at: string,
|
|
conclusion: "success" | "failure" | "neutral" | "cancelled" | "skipped" | "timed_out" | "action_required" | null,
|
|
head_commit: {
|
|
author: {
|
|
name: string,
|
|
},
|
|
message: string,
|
|
...
|
|
};
|
|
triggering_actor: {
|
|
login: string,
|
|
...
|
|
};
|
|
run_started_at: string,
|
|
};
|
|
|
|
|
|
type Artifact = {
|
|
id: number,
|
|
name: string,
|
|
url: string,
|
|
archive_download_url: string,
|
|
}
|
|
|
|
type WorkflowRuns = {
|
|
total_count: number,
|
|
workflow_runs: Array<WorkflowRun>,
|
|
}
|
|
|
|
type Artifacts = {
|
|
total_count: number,
|
|
artifacts: Array<Artifact>,
|
|
}
|
|
*/
|
|
|
|
let token;
|
|
let ciHeaders;
|
|
let artifacts;
|
|
let branch;
|
|
let baseTemporaryPath;
|
|
|
|
const reactNativeRepo = 'https://api.github.com/repos/facebook/react-native/';
|
|
const reactNativeActionsURL = `${reactNativeRepo}actions/runs`;
|
|
|
|
async function _getActionRunsOnBranch() /*: Promise<WorkflowRuns> */ {
|
|
const url = `${reactNativeActionsURL}?branch=${branch}&per_page=100`;
|
|
const options = {
|
|
method: 'GET',
|
|
headers: ciHeaders,
|
|
};
|
|
|
|
// $FlowIgnore[prop-missing] Conflicting .flowconfig in Meta's monorepo
|
|
// $FlowIgnore[incompatible-call]
|
|
const response = await fetch(url, options);
|
|
if (!response.ok) {
|
|
throw new Error(JSON.stringify(await response.json()));
|
|
}
|
|
|
|
const body = await response
|
|
// eslint-disable-next-line func-call-spacing
|
|
.json /*::<WorkflowRuns>*/
|
|
();
|
|
return body;
|
|
}
|
|
|
|
async function _getArtifacts(run_id /*: number */) /*: Promise<Artifacts> */ {
|
|
const url = `${reactNativeActionsURL}/${run_id}/artifacts?per_page=100`;
|
|
const options = {
|
|
method: 'GET',
|
|
headers: ciHeaders,
|
|
};
|
|
|
|
// $FlowIgnore[prop-missing] Conflicting .flowconfig in Meta's monorepo
|
|
// $FlowIgnore[incompatible-call]
|
|
const response = await fetch(url, options);
|
|
if (!response.ok) {
|
|
throw new Error(JSON.stringify(await response.json()));
|
|
}
|
|
|
|
const body = await response
|
|
// eslint-disable-next-line func-call-spacing
|
|
.json /*::<Artifacts>*/
|
|
();
|
|
return body;
|
|
}
|
|
|
|
function quote(text /*: string*/, prefix /*: string */ = ' > ') {
|
|
return text
|
|
.split('\n')
|
|
.map(line => prefix + line)
|
|
.join('\n');
|
|
}
|
|
|
|
// === Public Interface === //
|
|
async function initialize(
|
|
ciToken /*: string */,
|
|
baseTempPath /*: string */,
|
|
branchName /*: string */,
|
|
useLastSuccessfulPipeline /*: boolean */ = false,
|
|
) {
|
|
console.info('Getting GHA information');
|
|
baseTemporaryPath = baseTempPath;
|
|
exec(`mkdir -p ${baseTemporaryPath}`);
|
|
|
|
branch = branchName;
|
|
|
|
token = ciToken;
|
|
ciHeaders = {
|
|
Authorization: `Bearer ${token}`,
|
|
Accept: 'application/vnd.github+json',
|
|
'X-GitHub-Api-Version': '2022-11-28',
|
|
};
|
|
|
|
const testAllWorkflows = (await _getActionRunsOnBranch()).workflow_runs
|
|
.filter(w => w.name === 'Test All')
|
|
.sort((a, b) => {
|
|
// Date.getTime is needed to make Flow happy with arithmetic
|
|
return (
|
|
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
|
);
|
|
});
|
|
|
|
if (testAllWorkflows.length === 0) {
|
|
console.error('No Test-All workflow found');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`\nUsing github workflow run ${testAllWorkflows[0].run_number}`);
|
|
console.log(
|
|
`https://github.com/facebook/react-native/actions/runs/${testAllWorkflows[0].id}\n`,
|
|
);
|
|
|
|
let workflow = testAllWorkflows[0];
|
|
if (useLastSuccessfulPipeline) {
|
|
workflow =
|
|
testAllWorkflows.find(
|
|
wf => wf.status === 'completed' && wf.conclusion === 'success',
|
|
) ?? workflow;
|
|
}
|
|
|
|
const commit = workflow.head_commit;
|
|
const hours =
|
|
(new Date().getTime() - new Date(workflow.run_started_at).getTime()) /
|
|
(60 * 60 * 1000);
|
|
const started_by = workflow.triggering_actor.login;
|
|
|
|
console.log(
|
|
chalk.green(`The artifact being used is from a workflow started ${chalk.bold.magentaBright(hours.toFixed(0))} hours ago by ${chalk.bold.magentaBright(started_by)}:
|
|
|
|
Author: ${chalk.bold(commit.author.name)}
|
|
Message:
|
|
${chalk.magentaBright(quote(commit.message))}
|
|
`),
|
|
);
|
|
|
|
artifacts = await _getArtifacts(workflow.id);
|
|
}
|
|
|
|
function downloadArtifact(
|
|
artifactURL /*: string */,
|
|
destination /*: string */,
|
|
) {
|
|
exec(`rm -rf ${destination}`);
|
|
|
|
const command = `curl ${artifactURL} \
|
|
-Lo ${destination} \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "Authorization: Bearer ${token}" \
|
|
-H "X-GitHub-Api-Version: 2022-11-28"`;
|
|
|
|
exec(command, {stdio: 'inherit'});
|
|
}
|
|
|
|
async function artifactURLForJSCRNTesterAPK(
|
|
emulatorArch /*: string */,
|
|
) /*: Promise<string> */ {
|
|
return getArtifactURL('rntester-jsc-debug');
|
|
}
|
|
|
|
async function artifactURLForHermesRNTesterAPK(
|
|
emulatorArch /*: string */,
|
|
) /*: Promise<string> */ {
|
|
return getArtifactURL('rntester-hermes-debug');
|
|
}
|
|
|
|
async function artifactURLForJSCRNTesterApp() /*: Promise<string> */ {
|
|
return getArtifactURL('RNTesterApp-NewArch-JSC-Debug');
|
|
}
|
|
|
|
async function artifactURLForHermesRNTesterApp() /*: Promise<string> */ {
|
|
return getArtifactURL('RNTesterApp-NewArch-Hermes-Debug');
|
|
}
|
|
|
|
async function artifactURLForMavenLocal() /*: Promise<string> */ {
|
|
return getArtifactURL('maven-local');
|
|
}
|
|
|
|
async function getArtifactURL(
|
|
artifactName /*: string */,
|
|
) /*: Promise<string> */ {
|
|
const filteredUrls = artifacts.artifacts.filter(a => a.name === artifactName);
|
|
|
|
if (filteredUrls.length === 0) {
|
|
console.error(`No artifact found with name ${artifactName}`);
|
|
process.exit(1);
|
|
}
|
|
return filteredUrls[0].archive_download_url;
|
|
}
|
|
|
|
async function artifactURLHermesDebug() /*: Promise<string> */ {
|
|
return getArtifactURL('hermes-darwin-bin-Debug');
|
|
}
|
|
|
|
async function artifactURLForReactNative() /*: Promise<string> */ {
|
|
return getArtifactURL('react-native-package');
|
|
}
|
|
|
|
function baseTmpPath() /*: string */ {
|
|
return baseTemporaryPath;
|
|
}
|
|
|
|
module.exports = {
|
|
initialize,
|
|
downloadArtifact,
|
|
artifactURLForJSCRNTesterAPK,
|
|
artifactURLForHermesRNTesterAPK,
|
|
artifactURLForJSCRNTesterApp,
|
|
artifactURLForHermesRNTesterApp,
|
|
artifactURLForMavenLocal,
|
|
artifactURLHermesDebug,
|
|
artifactURLForReactNative,
|
|
baseTmpPath,
|
|
};
|