Skip to content
Closed
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
58 changes: 58 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,60 @@ jobs:
app: ios/build/Build/Products/Debug-iphonesimulator/HarnessPlayground.app
runner: ios
projectRoot: apps/playground

e2e-web:
name: E2E Web
runs-on: macos-latest
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

- 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: Install dependencies
run: |
pnpm install

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

- name: Run React Native Harness (Safari)
uses: ./actions/web
with:
runner: web:safari
projectRoot: apps/playground

# Test: "waitFor › should use custom interval and timeout options",
# is flacky on Chrome in GitHub CI runners
#
# - name: Run React Native Harness (Chrome)
# uses: ./actions/web
# with:
# runner: web:chrome
# projectRoot: apps/playground

# Test: "waitFor › should use custom interval and timeout options",
# fails on Firefox in GitHub CI runners
#
# - name: Run React Native Harness (Firefox)
# uses: ./actions/web
# with:
# runner: web:firefox
# projectRoot: apps/playground


33 changes: 17 additions & 16 deletions actions/shared/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
mod
));

// ../../node_modules/.pnpm/[email protected]/node_modules/picocolors/picocolors.js
// ../../node_modules/picocolors/picocolors.js
var require_picocolors = __commonJS({
"../../node_modules/.pnpm/[email protected]/node_modules/picocolors/picocolors.js"(exports2, module2) {
"../../node_modules/picocolors/picocolors.js"(exports2, module2) {
"use strict";
var p = process || {};
var argv = p.argv || [];
Expand Down Expand Up @@ -102,9 +102,9 @@ var require_picocolors = __commonJS({
}
});

// ../../node_modules/.pnpm/[email protected]/node_modules/sisteransi/src/index.js
// ../../node_modules/sisteransi/src/index.js
var require_src = __commonJS({
"../../node_modules/.pnpm/[email protected]/node_modules/sisteransi/src/index.js"(exports2, module2) {
"../../node_modules/sisteransi/src/index.js"(exports2, module2) {
"use strict";
var ESC = "\x1B";
var CSI = `${ESC}[`;
Expand Down Expand Up @@ -158,9 +158,9 @@ var require_src = __commonJS({
}
});

// ../../node_modules/.pnpm/[email protected]/node_modules/is-unicode-supported/index.js
// ../../node_modules/is-unicode-supported/index.js
var require_is_unicode_supported = __commonJS({
"../../node_modules/.pnpm/[email protected]/node_modules/is-unicode-supported/index.js"(exports2, module2) {
"../../node_modules/is-unicode-supported/index.js"(exports2, module2) {
"use strict";
module2.exports = () => {
if (process.platform !== "win32") {
Expand All @@ -172,7 +172,7 @@ var require_is_unicode_supported = __commonJS({
}
});

// ../../node_modules/.pnpm/[email protected]/node_modules/zod/dist/esm/v3/external.js
// ../../node_modules/zod/dist/esm/v3/external.js
var external_exports = {};
__export(external_exports, {
BRAND: () => BRAND,
Expand Down Expand Up @@ -284,7 +284,7 @@ __export(external_exports, {
void: () => voidType
});

// ../../node_modules/.pnpm/[email protected]/node_modules/zod/dist/esm/v3/helpers/util.js
// ../../node_modules/zod/dist/esm/v3/helpers/util.js
var util;
(function(util3) {
util3.assertEqual = (_) => {
Expand Down Expand Up @@ -418,7 +418,7 @@ var getParsedType = (data) => {
}
};

// ../../node_modules/.pnpm/[email protected]/node_modules/zod/dist/esm/v3/ZodError.js
// ../../node_modules/zod/dist/esm/v3/ZodError.js
var ZodIssueCode = util.arrayToEnum([
"invalid_type",
"invalid_literal",
Expand Down Expand Up @@ -535,7 +535,7 @@ ZodError.create = (issues) => {
return error;
};

// ../../node_modules/.pnpm/[email protected]/node_modules/zod/dist/esm/v3/locales/en.js
// ../../node_modules/zod/dist/esm/v3/locales/en.js
var errorMap = (issue, _ctx) => {
let message;
switch (issue.code) {
Expand Down Expand Up @@ -636,7 +636,7 @@ var errorMap = (issue, _ctx) => {
};
var en_default = errorMap;

// ../../node_modules/.pnpm/[email protected]/node_modules/zod/dist/esm/v3/errors.js
// ../../node_modules/zod/dist/esm/v3/errors.js
var overrideErrorMap = en_default;
function setErrorMap(map) {
overrideErrorMap = map;
Expand All @@ -645,7 +645,7 @@ function getErrorMap() {
return overrideErrorMap;
}

// ../../node_modules/.pnpm/[email protected]/node_modules/zod/dist/esm/v3/helpers/parseUtil.js
// ../../node_modules/zod/dist/esm/v3/helpers/parseUtil.js
var makeIssue = (params) => {
const { data, path: path4, errorMaps, issueData } = params;
const fullPath = [...path4, ...issueData.path || []];
Expand Down Expand Up @@ -755,14 +755,14 @@ var isDirty = (x) => x.status === "dirty";
var isValid = (x) => x.status === "valid";
var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;

// ../../node_modules/.pnpm/[email protected]/node_modules/zod/dist/esm/v3/helpers/errorUtil.js
// ../../node_modules/zod/dist/esm/v3/helpers/errorUtil.js
var errorUtil;
(function(errorUtil2) {
errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
})(errorUtil || (errorUtil = {}));

// ../../node_modules/.pnpm/[email protected]/node_modules/zod/dist/esm/v3/types.js
// ../../node_modules/zod/dist/esm/v3/types.js
var ParseInputLazyPath = class {
constructor(parent, value, path4, key) {
this._cachedPath = [];
Expand Down Expand Up @@ -4214,6 +4214,7 @@ var ConfigSchema = external_exports.object({
appRegistryComponentName: external_exports.string().min(1, "App registry component name is required"),
runners: external_exports.array(external_exports.any()).min(1, "At least one runner is required"),
defaultRunner: external_exports.string().optional(),
webSocketPort: external_exports.number().optional().default(3001),
bridgeTimeout: external_exports.number().min(1e3, "Bridge timeout must be at least 1 second").default(6e4),
resetEnvironmentBetweenTestFiles: external_exports.boolean().optional().default(true),
unstable__skipAlreadyIncludedModules: external_exports.boolean().optional().default(false),
Expand All @@ -4233,7 +4234,7 @@ var ConfigSchema = external_exports.object({
// ../tools/dist/logger.js
var import_node_util2 = __toESM(require("util"), 1);

// ../../node_modules/.pnpm/@[email protected]/node_modules/@clack/core/dist/index.mjs
// ../../node_modules/@clack/core/dist/index.mjs
var import_node_process = require("process");
var V = __toESM(require("readline"), 1);
var import_node_readline = __toESM(require("readline"), 1);
Expand All @@ -4251,7 +4252,7 @@ var C = { actions: new Set(gt), aliases: /* @__PURE__ */ new Map([["k", "up"], [
var At = globalThis.process.platform.startsWith("win");
var G = Symbol("clack:cancel");

// ../../node_modules/.pnpm/@[email protected]/node_modules/@clack/prompts/dist/index.mjs
// ../../node_modules/@clack/prompts/dist/index.mjs
var import_picocolors = __toESM(require_picocolors(), 1);
var import_node_process2 = __toESM(require("process"), 1);
var import_node_fs = require("fs");
Expand Down
27 changes: 27 additions & 0 deletions actions/web/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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
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: Run E2E tests
shell: bash
working-directory: ${{ inputs.projectRoot }}
run: |
pnpm react-native-harness --harnessRunner ${{ inputs.runner }}
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";
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,6 +1,7 @@
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);

Expand All @@ -17,7 +18,32 @@ const customConfig = {
cacheVersion: '@react-native-harness/playground',
resolver: {
unstable_enablePackageExports: true,

},
server: {
...(defaultConfig.server || {}),
enhanceMiddleware: (middleware, server) => {
return (req, res, next) => {
// Serve our HTML shell at / and /index.html
if (req.url === '/' || req.url === '/index.html') {
const htmlPath = path.join(projectRoot, 'web', '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 +52,30 @@ module.exports = withNxMetro(mergeConfig(defaultConfig, customConfig), {
path.resolve(projectRoot, 'node_modules'),
path.resolve(monorepoRoot, 'node_modules'),
],
}).then((config) => {
const defaultResolveRequest = config.resolver.resolveRequest;
config.resolver.resolveRequest = (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);
};

return config;
});
7 changes: 5 additions & 2 deletions apps/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
},
"dependencies": {
"react": "19.1.1",
"react-native": "0.82.1"
"react-dom": "19.1.1",
"react-native": "0.82.1",
"react-native-web": "^0.21.2"
},
"devDependencies": {
"react-native-harness": "workspace:*",
Expand All @@ -22,6 +24,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:*"
}
}
18 changes: 18 additions & 0 deletions apps/playground/rn-harness.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {
vegaPlatform,
vegaEmulator,
} from '@react-native-harness/platform-vega';
import {
webPlatform,
} from '@react-native-harness/platform-web';

const config = {
entryPoint: './index.js',
Expand Down Expand Up @@ -48,6 +51,21 @@ const config = {
device: vegaEmulator('VegaTV_1'),
bundleId: 'com.playground',
}),
webPlatform({
name: 'web:chrome',
browserName: 'chrome',
appUrl: 'http://localhost:8081/index.html',
}),
webPlatform({
name: 'web:firefox',
browserName: 'firefox',
appUrl: 'http://localhost:8081/index.html',
}),
webPlatform({
name: 'web:safari',
browserName: 'safari',
appUrl: 'http://localhost:8081/index.html',
}),
],
defaultRunner: 'android',
bridgeTimeout: 120000,
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 @@ -33,6 +33,9 @@
"react-native-harness.d.ts"
],
"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
Loading