Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 94 additions & 12 deletions .github/workflows/runtime_build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -737,9 +737,45 @@ jobs:
name: react-devtools
pattern: react-devtools-*

runtime_playwright_chromium_cache:
name: Cache Runtime Playwright Chromium
needs: runtime_node_modules_cache
runs-on: ubuntu-latest
outputs:
playwright_version: ${{ steps.playwright_version.outputs.playwright_version }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Restore cached node_modules
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- name: Install Playwright Chromium
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
run: npx playwright install chromium

run_devtools_e2e_tests:
name: Run DevTools e2e tests
needs: [build_and_lint, runtime_node_modules_cache]
name: Run React DevTools browser extension e2e tests
needs: [build_and_lint, runtime_playwright_chromium_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -766,18 +802,12 @@ jobs:
pattern: _build_*
path: build
merge-multiple: true
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
- name: Restore Playwright Chromium
uses: actions/cache/restore@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- name: Playwright install deps
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ needs.runtime_playwright_chromium_cache.outputs.playwright_version }}
fail-on-cache-miss: true
- run: ./scripts/ci/run_devtools_e2e_tests.js
env:
RELEASE_CHANNEL: experimental
Expand All @@ -788,6 +818,58 @@ jobs:
path: tmp/playwright-artifacts
if-no-files-found: warn

run_react_devtools_cdt_mcp_e2e_tests:
name: Run react-devtools-cdt-mcp e2e tests
needs: [build_and_lint, runtime_playwright_chromium_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Restore cached node_modules
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- name: Restore Playwright Chromium
uses: actions/cache/restore@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ needs.runtime_playwright_chromium_cache.outputs.playwright_version }}
fail-on-cache-miss: true
- name: Check Playwright Chromium version
id: playwright_chromium
run: |
echo "executable_path=$(node -e 'process.stdout.write(require("playwright").chromium.executablePath())')" >> "$GITHUB_OUTPUT"
"$(node -e 'process.stdout.write(require("playwright").chromium.executablePath())')" --version
- name: Run React DevTools CDT MCP e2e tests
run: yarn --cwd packages/react-devtools-cdt-mcp test:e2e:ci
env:
CHROME_EXECUTABLE_PATH: ${{ steps.playwright_chromium.outputs.executable_path }}
- name: Archive React DevTools CDT MCP e2e logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: react-devtools-cdt-mcp-e2e-logs
path: tmp/react-devtools-cdt-mcp-e2e
if-no-files-found: warn

# ----- SIZEBOT -----
sizebot:
if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' && github.event.pull_request.base.ref == 'main' }}
Expand Down
5 changes: 5 additions & 0 deletions compiler/crates/react_compiler_lowering/src/hir_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,11 @@ impl<'a> HirBuilder<'a> {
.scope_info
.find_binding_in_descendants(name, self.component_scope)
{
// When component_scope == program_scope (e2e path where scope info
// is extracted from the function itself), any binding found is local.
if self.component_scope == self.scope_info.program_scope {
return true;
}
return binding.scope != self.scope_info.program_scope;
}
false
Expand Down
20 changes: 17 additions & 3 deletions compiler/scripts/test-rust-port.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@ function compileFixture(mode: CompileMode, fixturePath: string): CompileOutput {
for (const item of details) {
if (item.kind === 'error') {
lines.push(
` error: ${formatLoc(item.loc)}${item.message ? ': ' + item.message : ''}`,
` error: ${formatLoc(item.loc)}${
item.message ? ': ' + item.message : ''
}`,
);
} else if (item.kind === 'hint') {
lines.push(` hint: ${item.message ?? ''}`);
Expand Down Expand Up @@ -370,7 +372,9 @@ function formatLogItem(item: LogItem): string {
if (item.kind === 'entry') {
return `## ${item.name}\n${item.value}`;
} else {
return `[${item.eventKind}]${item.fnName ? ' ' + item.fnName : ''}: ${item.detail}`;
return `[${item.eventKind}]${item.fnName ? ' ' + item.fnName : ''}: ${
item.detail
}`;
}
}

Expand Down Expand Up @@ -627,7 +631,17 @@ function findDivergencePass(tsLog: LogItem[], rustLog: LogItem[]): string {
const tsFormatted = normalizeIds(formatLog(ts.log));
const rustFormatted = normalizeIds(formatLog(rust.log));

if (tsFormatted === rustFormatted) {
// When both compilers throw an error (same final outcome), tolerate
// differences in debug output. Rust's fault-tolerant pipeline emits
// partial debug IR before reaching the same fatal error that TS's
// throw-immediate approach reports with no debug output at all.
const bothErrored =
ts.error != null &&
rust.error != null &&
ts.code == null &&
rust.code == null;

if (tsFormatted === rustFormatted || bothErrored) {
passed++;
// Count as passed for all passes that appeared in the log
const seenPasses = new Set<string>();
Expand Down
125 changes: 121 additions & 4 deletions packages/react-devtools-cdt-mcp/e2e/run.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const childProcess = require('child_process');
const fs = require('fs');
const http = require('http');
const net = require('net');
const os = require('os');
const path = require('path');

// eslint-disable-next-line no-undef
Expand Down Expand Up @@ -220,11 +221,69 @@ function spawnLogged(
stdio: ['ignore', 'pipe', 'pipe'],
});
appendLog(options.logFile, formatCommand(command, args));
child.on('error', error => {
appendLog(
options.logFile,
`Failed to spawn ${command}: ${
error.stack || error.message || String(error)
}\n`
);
});
child.stdout.on('data', chunk => appendLog(options.logFile, chunk));
child.stderr.on('data', chunk => appendLog(options.logFile, chunk));
return child;
}

function waitForExit(child: ChildProcess, timeout: number): Promise<void> {
return new Promise(resolve => {
let done = false;
const finish = () => {
if (done) {
return;
}
done = true;
clearTimeout(timer);
resolve();
};
const timer = setTimeout(() => {
if (child.exitCode == null) {
child.kill('SIGKILL');
}
finish();
}, timeout);

if (child.exitCode != null) {
finish();
return;
}
child.once('exit', finish);
child.once('error', finish);
});
}

async function removePathWithRetries(
targetPath: string,
logFile: string
): Promise<void> {
for (let attempt = 0; attempt < 5; attempt++) {
try {
fs.rmSync(targetPath, {force: true, recursive: true});
return;
} catch (error) {
if (attempt === 4) {
appendLog(
logFile,
`Failed to remove ${targetPath}: ${
error.stack || error.message || String(error)
}\n`
);
return;
}
await sleep(250);
}
}
}

function runCommand(
command: string,
args: Array<string>,
Expand Down Expand Up @@ -275,6 +334,40 @@ function runCommand(
});
}

function startDebuggableChrome(
chromeExecutablePath: string,
remoteDebuggingPort: number,
logFile: string
): {profileDir: string, process: ChildProcess} {
const profileDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'react-devtools-cdt-mcp-chrome-')
);
appendLog(logFile, `chromeUserDataDir=${profileDir}\n`);

const args = [
'--headless=new',
`--remote-debugging-port=${remoteDebuggingPort}`,
'--remote-debugging-address=127.0.0.1',
`--user-data-dir=${profileDir}`,
'--no-first-run',
'--no-default-browser-check',
'about:blank',
];

if (process.platform === 'linux') {
args.splice(1, 0, '--no-sandbox', '--disable-setuid-sandbox');
}

return {
profileDir,
process: spawnLogged(chromeExecutablePath, args, {
cwd: PACKAGE_DIR,
env: process.env,
logFile,
}),
};
}

function getChromeDevToolsBin(): string {
let packageJsonPath: string;
try {
Expand Down Expand Up @@ -1078,6 +1171,8 @@ async function main(): Promise<void> {
const appUrl = `http://127.0.0.1:${port}/`;

let fixture: ?ChildProcess;
let debuggableChrome: ?ChildProcess;
let debuggableChromeProfile: ?string;
const runChrome = (args: Array<string>): Promise<CommandResult> =>
runCommand(
process.execPath,
Expand Down Expand Up @@ -1117,14 +1212,25 @@ async function main(): Promise<void> {
const startArgs = [
'start',
'--categoryExperimentalThirdParty=true',
'--headless=true',
'--isolated=true',
'--usageStatistics=false',
'--logFile',
chromeLog,
];
if (process.env.CHROME_EXECUTABLE_PATH) {
startArgs.push('--executablePath', process.env.CHROME_EXECUTABLE_PATH);
const chromeExecutablePath = process.env.CHROME_EXECUTABLE_PATH;
if (chromeExecutablePath != null) {
const remoteDebuggingPort = await getFreePort();
const launchedChrome = startDebuggableChrome(
chromeExecutablePath,
remoteDebuggingPort,
chromeLog
);
debuggableChrome = launchedChrome.process;
debuggableChromeProfile = launchedChrome.profileDir;
const browserUrl = `http://127.0.0.1:${remoteDebuggingPort}`;
await waitForHttp(`${browserUrl}/json/version`, 30000);
startArgs.push('--browserUrl', browserUrl);
} else {
startArgs.push('--headless=true', '--isolated=true');
}
log('Starting chrome-devtools daemon...');
await chrome.run(startArgs);
Expand All @@ -1137,6 +1243,17 @@ async function main(): Promise<void> {
} catch (error) {
appendLog(cliLog, `Failed to stop chrome-devtools: ${error.stack}\n`);
}
if (debuggableChrome) {
try {
debuggableChrome.kill('SIGTERM');
await waitForExit(debuggableChrome, 5000);
} catch (error) {
appendLog(chromeLog, `Failed to stop Chrome: ${error.stack}\n`);
}
}
if (debuggableChromeProfile) {
await removePathWithRetries(debuggableChromeProfile, chromeLog);
}
if (fixture && fixture.pid) {
try {
process.kill(-fixture.pid, 'SIGTERM');
Expand Down
Loading