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
48 changes: 48 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ on:
- all
- android
- ios
- web
pull_request:
types: [ready_for_review]
branches:
Expand Down Expand Up @@ -189,3 +190,50 @@ jobs:
app: ios/build/Build/Products/Debug-iphonesimulator/HarnessPlayground.app
runner: ios
projectRoot: apps/playground

e2e-web:
name: E2E Web
runs-on: ubuntu-22.04
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main') || (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web')) }}
env:
HARNESS_DEBUG: true

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
fetch-depth: 0

- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: latest

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24.10.0'
cache: 'pnpm'

- name: Metro cache
uses: actions/cache@v4
with:
path: apps/playground/node_modules/.cache/rn-harness/metro-cache
key: metro-cache-${{ hashFiles('apps/playground/node_modules/.cache/rn-harness/metro-cache/**/*') }}
restore-keys: |
metro-cache

- name: Install dependencies
run: |
pnpm install

- name: Build packages
run: |
pnpm nx run-many -t build --projects="packages/*"

- name: Run React Native Harness
uses: ./actions/web
with:
runner: chromium
projectRoot: apps/playground
2 changes: 1 addition & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
strict-peer-dependencies=false
auto-install-peers=true
auto-install-peers=false
node-linker=hoisted
5 changes: 5 additions & 0 deletions .nx/version-plans/version-plan-1769076452249.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
__default__: prerelease
---

Added support for web platform with all functionalities supported by the native equivalents, including UI testing capabilities.
44 changes: 44 additions & 0 deletions actions/web/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: React Native Harness for Web
description: Run React Native Harness tests on Web
inputs:
runner:
description: The runner to use
required: true
type: string
projectRoot:
description: The project root directory
required: false
type: string
uploadVisualTestArtifacts:
description: Whether to upload visual test diff and actual images as artifacts
required: false
type: boolean
default: 'true'
runs:
using: 'composite'
steps:
- name: Load React Native Harness configuration
id: load-config
shell: bash
env:
INPUT_RUNNER: ${{ inputs.runner }}
INPUT_PROJECTROOT: ${{ inputs.projectRoot }}
run: |
node ${{ github.action_path }}/../shared/index.cjs
- name: Install Playwright Browsers
shell: bash
run: npx playwright install --with-deps chromium
- name: Run E2E tests
shell: bash
working-directory: ${{ inputs.projectRoot }}
run: |
pnpm react-native-harness --harnessRunner ${{ inputs.runner }}
- name: Upload visual test artifacts
if: always() && inputs.uploadVisualTestArtifacts == 'true'
uses: actions/upload-artifact@v4
with:
name: visual-test-diffs-chromium
path: |
${{ inputs.projectRoot }}/**/__image_snapshots__/**/*-diff.png
${{ inputs.projectRoot }}/**/__image_snapshots__/**/*-actual.png
if-no-files-found: ignore
1 change: 1 addition & 0 deletions actions/web/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"use strict";
27 changes: 27 additions & 0 deletions apps/playground/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Harness Playground</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
html,
body {
height: 100%;
}

body {
overflow: hidden;
}

#root {
display: flex;
height: 100%;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="http://localhost:8081/index.bundle?platform=web&dev=true&entryFile=index.js"></script>
</body>
</html>
8 changes: 7 additions & 1 deletion apps/playground/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
* @format
*/

import { AppRegistry } from 'react-native';
import { AppRegistry, Platform } from 'react-native';
import App from './src/app/App';
import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () => App);

if (Platform.OS === 'web') {
AppRegistry.runApplication(appName, {
rootTag: document.getElementById('root'),
});
}
52 changes: 52 additions & 0 deletions apps/playground/metro.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
const { withNxMetro } = require('@nx/react-native');
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const path = require('path');
const fs = require('fs');

const defaultConfig = getDefaultConfig(__dirname);

const projectRoot = __dirname;
const monorepoRoot = path.resolve(projectRoot, '../..');

const getCustomResolver = (defaultResolveRequest) => (context, moduleName, platform) => {
if (platform === 'web') {
if (moduleName.includes('NativeSourceCode') ||
moduleName.includes('NativePlatformConstants') ||
moduleName.includes('NativeDevSettings') ||
moduleName.includes('NativeLogBox') ||
moduleName.includes('NativeRedBox')
) {
return {
type: 'empty',
};
} else if (moduleName === 'react-native') {
return {
type: 'sourceFile',
filePath: require.resolve('react-native-web'),
};
}
}

// Everything else: default behavior
return defaultResolveRequest(context, moduleName, platform);
};

/**
* Metro configuration
* https://reactnative.dev/docs/metro
Expand All @@ -18,6 +42,30 @@ const customConfig = {
resolver: {
unstable_enablePackageExports: true,
},
server: {
...(defaultConfig.server || {}),
enhanceMiddleware: (middleware) => {
return (req, res, next) => {
if (req.url === '/' || req.url === '/index.html') {
const htmlPath = path.join(projectRoot, 'index.html');

fs.readFile(htmlPath, 'utf8', (err, data) => {
if (err) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Error loading index.html: ' + err.message);
return;
}

res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data);
});
return;
}

return middleware(req, res, next);
};
},
},
};

module.exports = withNxMetro(mergeConfig(defaultConfig, customConfig), {
Expand All @@ -26,4 +74,8 @@ module.exports = withNxMetro(mergeConfig(defaultConfig, customConfig), {
path.resolve(projectRoot, 'node_modules'),
path.resolve(monorepoRoot, 'node_modules'),
],
}).then((config) => {
// Nx overrides the resolveRequest, so we need to override it after the merge.
config.resolver.resolveRequest = getCustomResolver(config.resolver.resolveRequest);
return config;
});
9 changes: 6 additions & 3 deletions apps/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
"test:harness": "jest --selectProjects react-native-harness"
},
"dependencies": {
"react": "19.1.1",
"react-native": "0.82.1"
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "0.82.1",
"react-native-web": "^0.21.2"
},
"devDependencies": {
"react-native-harness": "workspace:*",
Expand All @@ -24,6 +26,7 @@
"jest": "^30.2.0",
"@react-native-harness/platform-android": "workspace:*",
"@react-native-harness/platform-apple": "workspace:*",
"@react-native-harness/platform-vega": "workspace:*"
"@react-native-harness/platform-vega": "workspace:*",
"@react-native-harness/platform-web": "workspace:*"
}
}
13 changes: 13 additions & 0 deletions apps/playground/rn-harness.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import {
vegaPlatform,
vegaEmulator,
} from '@react-native-harness/platform-vega';
import {
webPlatform,
chromium,
chrome,
} from '@react-native-harness/platform-web';

const config = {
entryPoint: './index.js',
Expand Down Expand Up @@ -48,6 +53,14 @@ const config = {
device: vegaEmulator('VegaTV_1'),
bundleId: 'com.playground',
}),
webPlatform({
name: 'web',
browser: chrome('http://localhost:8081/index.html', { headless: false }),
}),
webPlatform({
name: 'chromium',
browser: chromium('http://localhost:8081/index.html', { headless: true }),
}),
],
defaultRunner: 'android',
bridgeTimeout: 120000,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/playground/src/__tests__/ui/screenshot.harness.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('Screenshot', () => {
alignItems: 'center',
}}
>
<Text style={{ color: 'white' }}>Target</Text>
<View style={{ width: 50, height: 50, backgroundColor: 'white' }} />
</View>
</View>
</View>
Expand Down
3 changes: 3 additions & 0 deletions apps/playground/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"],
"references": [
{
"path": "../../packages/platform-web/tsconfig.lib.json"
},
{
"path": "../../packages/platform-vega/tsconfig.lib.json"
},
Expand Down
3 changes: 3 additions & 0 deletions apps/playground/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"files": [],
"include": [],
"references": [
{
"path": "../../packages/platform-web"
},
{
"path": "../../packages/platform-vega"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/bridge/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export type {
} from './shared/bundler.js';

export type DeviceDescriptor = {
platform: 'ios' | 'android' | 'vega';
platform: 'ios' | 'android' | 'vega' | 'web';
manufacturer: string;
model: string;
osVersion: string;
Expand Down
44 changes: 44 additions & 0 deletions packages/github-action/src/web/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: React Native Harness for Web
description: Run React Native Harness tests on Web
inputs:
runner:
description: The runner to use
required: true
type: string
projectRoot:
description: The project root directory
required: false
type: string
uploadVisualTestArtifacts:
description: Whether to upload visual test diff and actual images as artifacts
required: false
type: boolean
default: 'true'
runs:
using: 'composite'
steps:
- name: Load React Native Harness configuration
id: load-config
shell: bash
env:
INPUT_RUNNER: ${{ inputs.runner }}
INPUT_PROJECTROOT: ${{ inputs.projectRoot }}
run: |
node ${{ github.action_path }}/../shared/index.cjs
- name: Install Playwright Browsers
shell: bash
run: npx playwright install --with-deps chromium
- name: Run E2E tests
shell: bash
working-directory: ${{ inputs.projectRoot }}
run: |
pnpm react-native-harness --harnessRunner ${{ inputs.runner }}
- name: Upload visual test artifacts
if: always() && inputs.uploadVisualTestArtifacts == 'true'
uses: actions/upload-artifact@v4
with:
name: visual-test-diffs-chromium
path: |
${{ inputs.projectRoot }}/**/__image_snapshots__/**/*-diff.png
${{ inputs.projectRoot }}/**/__image_snapshots__/**/*-actual.png
if-no-files-found: ignore
1 change: 1 addition & 0 deletions packages/github-action/src/web/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// This file is intentionally empty.
2 changes: 1 addition & 1 deletion packages/github-action/tsup.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'node:fs';
import path from 'node:path';

const OUT_DIR = path.resolve('../../actions');
const TARGETS = ['ios', 'android'];
const TARGETS = ['ios', 'android', 'web'];

const packageJson = JSON.parse(
fs.readFileSync(path.resolve('./package.json'), 'utf8')
Expand Down
2 changes: 1 addition & 1 deletion packages/platform-android/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const physicalAndroidDevice = (
export const androidPlatform = (
config: AndroidPlatformConfig
): HarnessPlatform<AndroidPlatformConfig> => ({
name: 'android',
name: config.name,
config,
runner: import.meta.resolve('./runner.js'),
});
Loading