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/picocolors@1.1.1/node_modules/picocolors/picocolors.js
// ../../node_modules/picocolors/picocolors.js
var require_picocolors = __commonJS({
"../../node_modules/.pnpm/picocolors@1.1.1/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/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
// ../../node_modules/sisteransi/src/index.js
var require_src = __commonJS({
"../../node_modules/.pnpm/sisteransi@1.0.5/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/is-unicode-supported@0.1.0/node_modules/is-unicode-supported/index.js
// ../../node_modules/is-unicode-supported/index.js
var require_is_unicode_supported = __commonJS({
"../../node_modules/.pnpm/is-unicode-supported@0.1.0/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/zod@3.25.67/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/zod@3.25.67/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/zod@3.25.67/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/zod@3.25.67/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/zod@3.25.67/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/zod@3.25.67/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/zod@3.25.67/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/zod@3.25.67/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/@clack+core@1.0.0-alpha.5/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/@clack+prompts@1.0.0-alpha.5/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