Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ jobs:
arch: x86_64
profile: pixel_7
avd-name: e2e
disable-animations: false
working-directory: apps/tester-federation-v2
script: |
adb shell settings put global hide_error_dialogs 1
Expand Down
1 change: 1 addition & 0 deletions agent_context/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ works, and is kept in sync with the implementation as it evolves.
| Folder | Topic |
| --- | --- |
| [rspackv2-jul2026](./rspackv2-jul2026/design.md) | Dual Rspack 1.x/2.x support |
| [e2e-oct2026](./e2e-oct2026/design.md) | e2e tests on an emulator, on CI |
254 changes: 254 additions & 0 deletions agent_context/e2e-oct2026/design.md

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions apps/tester-federation-v2/babel.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: ['react-native-worklets/plugin'],
comments: true,
overrides: [
{
// Regex instead of a relative string: babel resolves strings against the
// launch cwd, and the rspack CLI launches from the repo root.
test: /[\\/]src[\\/]host[\\/]/,
plugins: [
[
'@babel/plugin-transform-react-jsx',
{ runtime: 'automatic', importSource: 'nativewind' },
],
],
},
],
};
44 changes: 44 additions & 0 deletions apps/tester-federation-v2/configs/hostAssetRules.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import path from 'node:path';
import * as Repack from '@callstack/repack';

const BITMAP_EXTENSIONS = Repack.getAssetExtensionsRegExp(
Repack.ASSET_EXTENSIONS.filter((ext) => ext !== 'svg')
);

export function buildHostAssetRules(context: string) {
const localAssets = path.join(context, 'src/host/assets/localAssets');
const inlineAssets = path.join(context, 'src/host/assets/inlineAssets');

return [
{
test: BITMAP_EXTENSIONS,
exclude: [localAssets, inlineAssets],
use: {
loader: '@callstack/repack/assets-loader',
options: { inline: true, maxInlineSize: 90 * 1024 },
},
},
{
test: /\.svg$/,
use: [
{
loader: '@svgr/webpack',
options: { native: true, dimensions: false },
},
],
},
{
test: BITMAP_EXTENSIONS,
include: [localAssets],
use: '@callstack/repack/assets-loader',
},
{
test: BITMAP_EXTENSIONS,
include: [inlineAssets],
use: {
loader: '@callstack/repack/assets-loader',
options: { inline: true },
},
},
];
}
16 changes: 12 additions & 4 deletions apps/tester-federation-v2/configs/rspack.host-app.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as Repack from '@callstack/repack';
import { NativeWindPlugin } from '@callstack/repack-plugin-nativewind';
import { ReanimatedPlugin } from '@callstack/repack-plugin-reanimated';
import rspack from '@rspack/core';

import { buildHostAssetRules } from './hostAssetRules.mts';
import { buildSharedSingletons } from './sharedSingletons.mts';

export default Repack.defineRspackConfig((env) => {
Expand Down Expand Up @@ -28,15 +31,19 @@ export default Repack.defineRspackConfig((env) => {
},
type: 'javascript/auto',
},
...Repack.getAssetTransformRules({
inline: true,
maxInlineSize: 90 * 1024,
}),
...buildHostAssetRules(context!),
],
},
plugins: [
new NativeWindPlugin({
cssInteropOptions: { inlineRem: 16 },
}),
new Repack.RepackPlugin({
extraChunks: [
{
include: /^host-async-local$/,
type: 'local',
},
{
include: /.*/,
type: 'remote',
Expand All @@ -62,6 +69,7 @@ export default Repack.defineRspackConfig((env) => {
process.env.WITH_PRELOAD === 'true' ||
process.env.WITH_PRELOAD === '1',
}),
new ReanimatedPlugin({ unstable_disableTransform: true }),
],
};
});
15 changes: 12 additions & 3 deletions apps/tester-federation-v2/configs/webpack.host-app.mts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// @ts-check
import * as Repack from '@callstack/repack';
import { NativeWindPlugin } from '@callstack/repack-plugin-nativewind';
import { ReanimatedPlugin } from '@callstack/repack-plugin-reanimated';
import webpack from 'webpack';

import { buildHostAssetRules } from './hostAssetRules.mts';
import { buildSharedSingletons } from './sharedSingletons.mts';

export default Repack.defineWebpackConfig((env) => {
Expand All @@ -25,21 +28,26 @@ export default Repack.defineWebpackConfig((env) => {
use: '@callstack/repack/babel-swc-loader',
type: 'javascript/auto',
},
...Repack.getAssetTransformRules(),
...buildHostAssetRules(context!),
],
},
plugins: [
// @ts-expect-error
new NativeWindPlugin({
cssInteropOptions: { inlineRem: 16 },
}),
new Repack.RepackPlugin({
extraChunks: [
{
include: /^host-async-local$/,
type: 'local',
},
{
include: /.*/,
type: 'remote',
outputPath: `build/host-app/${platform}/output-remote`,
},
],
}),
// @ts-expect-error
new Repack.plugins.ModuleFederationPluginV2({
name: 'HostApp',
filename: 'HostApp.container.js.bundle',
Expand All @@ -58,6 +66,7 @@ export default Repack.defineWebpackConfig((env) => {
process.env.WITH_PRELOAD === 'true' ||
process.env.WITH_PRELOAD === '1',
}),
new ReanimatedPlugin({ unstable_disableTransform: true }),
],
};
});
2 changes: 0 additions & 2 deletions apps/tester-federation-v2/configs/webpack.mini-app.mts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export default Repack.defineWebpackConfig((env) => {
],
},
plugins: [
// @ts-expect-error
new Repack.RepackPlugin({
extraChunks: [
{
Expand All @@ -38,7 +37,6 @@ export default Repack.defineWebpackConfig((env) => {
},
],
}),
// @ts-expect-error
new Repack.plugins.ModuleFederationPluginV2({
name: 'MiniApp',
filename: 'MiniApp.container.js.bundle',
Expand Down
19 changes: 19 additions & 0 deletions apps/tester-federation-v2/e2e/async-chunk.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { test } from '@e2e-dev/mobile';
import { expect } from 'e2e';

test('Async chunk loads through ScriptManager and renders', async ({
app,
screen,
}) => {
await app.open();
await expect(screen.getByTestId('home-screen')).toBeVisible({
timeout: 30_000,
});

const entry = screen.getByTestId('home-async-chunk-button');
await screen.scrollUntilVisible(entry);
await entry.tap();
await expect(screen.getByTestId('async-chunk-screen')).toBeVisible();

await expect(screen.getByTestId('async-chunk-content')).toBeVisible();
});
21 changes: 21 additions & 0 deletions apps/tester-federation-v2/e2e/gallery.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { test } from '@e2e-dev/mobile';
import { expect } from 'e2e';

test('Gallery MiniApp loads and renders its own navigator', async ({
app,
screen,
}) => {
await app.open();
await expect(screen.getByTestId('home-screen')).toBeVisible({
timeout: 30_000,
});

const entry = screen.getByTestId('home-gallery-button');
await screen.scrollUntilVisible(entry);
await entry.tap();

await expect(screen.getByTestId('miniapp-gallery')).toBeVisible({
timeout: 45_000,
});
await expect(screen.getByText('Picture 1')).toBeVisible();
});
44 changes: 44 additions & 0 deletions apps/tester-federation-v2/e2e/host-assets.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { test } from '@e2e-dev/mobile';
import { expect } from 'e2e';

test('Host Assets screen renders', async ({ app, screen }) => {
await app.open();
await expect(screen.getByTestId('home-screen')).toBeVisible({
timeout: 30_000,
});

const entry = screen.getByTestId('home-host-assets-button');
await screen.scrollUntilVisible(entry);
await entry.tap();
await expect(screen.getByTestId('host-assets-screen')).toBeVisible();

const pic1Mode = screen.getByTestId('host-asset-pic-1-mode-report');
const pic2Mode = screen.getByTestId('host-asset-pic-2-mode-report');
const localMode = screen.getByTestId('host-asset-local-mode-report');
const inlineMode = screen.getByTestId('host-asset-inline-mode-report');
const localUri = screen.getByTestId('host-asset-local-uri-report');

await screen.scrollUntilVisible(pic1Mode);
await expect(pic1Mode).toHaveText('mode: Inlined');
await screen.scrollUntilVisible(pic2Mode);
await expect(pic2Mode).toHaveText('mode: Emitted');
await screen.scrollUntilVisible(localMode);
await expect(localMode).toHaveText('mode: Emitted');
await screen.scrollUntilVisible(inlineMode);
await expect(inlineMode).toHaveText('mode: Inlined');

await screen.scrollUntilVisible(localUri);
const uriText = (await localUri.textContent()) ?? '';
const assetUrl = new URL(uriText.slice('uri: '.length));
expect(assetUrl.protocol).toBe('http:');
expect(assetUrl.port).toBe('8081');
// Android resolves the dev server as 10.0.2.2 and iOS as localhost
expect(['localhost', '10.0.2.2']).toContain(assetUrl.hostname);

const probeHost =
assetUrl.hostname === '10.0.2.2' ? 'localhost' : assetUrl.hostname;
const response = await fetch(
`${assetUrl.protocol}//${probeHost}:${assetUrl.port}${assetUrl.pathname}${assetUrl.search}`
);
expect(response.status).toBe(200);
});
34 changes: 34 additions & 0 deletions apps/tester-federation-v2/e2e/miniapp-install.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { test } from '@e2e-dev/mobile';
import { expect } from 'e2e';

test('install, show, and remove the host mini-app chunk', async ({
app,
screen,
}) => {
await app.open();
await expect(screen.getByTestId('home-screen')).toBeVisible({
timeout: 30_000,
});

const entry = screen.getByTestId('home-miniapp-install-button');
await screen.scrollUntilVisible(entry);
await entry.tap();
await expect(screen.getByTestId('miniapp-install-screen')).toBeVisible();

await screen.getByTestId('miniapp-install-button').tap();
await expect(screen.getByTestId('miniapp-install-button')).toBeDisabled();

await screen.getByTestId('miniapp-toggle-button').tap();
await expect(screen.getByTestId('miniapp-install-content')).toBeVisible();

await screen.getByTestId('miniapp-toggle-button').tap();
await expect(screen.getByTestId('miniapp-install-content')).toBeHidden();

await screen.getByTestId('miniapp-toggle-button').tap();
await expect(screen.getByTestId('miniapp-install-content')).toBeVisible();

await screen.getByTestId('miniapp-uninstall-button').tap();
await expect(screen.getByTestId('miniapp-install-content')).toBeHidden();
await expect(screen.getByTestId('miniapp-install-button')).toBeEnabled();
await expect(screen.getByTestId('miniapp-toggle-button')).toBeDisabled();
});
30 changes: 30 additions & 0 deletions apps/tester-federation-v2/e2e/nativewind.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { test } from '@e2e-dev/mobile';
import { expect } from 'e2e';

test('NativeWind styles render, respond and switch to dark', async ({
app,
screen,
}) => {
await app.open();
await expect(screen.getByTestId('home-screen')).toBeVisible({
timeout: 30_000,
});

const entry = screen.getByTestId('home-nativewind-button');
await screen.scrollUntilVisible(entry);
await entry.tap();
await expect(screen.getByTestId('nativewind-screen')).toBeVisible();

const padding = screen.getByTestId('nativewind-padding-report');
await expect(padding).toHaveText('padding-applied: 16');

const calcWidth = screen.getByTestId('nativewind-calc-width-report');
await expect(calcWidth).toHaveText('calc-width: 248');

const colorScheme = screen.getByTestId('nativewind-color-scheme-report');
await expect(colorScheme).toHaveText('color-scheme: light');
await screen.getByTestId('nativewind-dark-mode-button').tap();
await expect(colorScheme).toHaveText('color-scheme: dark');
await screen.getByTestId('nativewind-dark-mode-button').tap();
await expect(colorScheme).toHaveText('color-scheme: light');
});
26 changes: 26 additions & 0 deletions apps/tester-federation-v2/e2e/reanimated.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { test } from '@e2e-dev/mobile';
import { expect } from 'e2e';

test('Reanimated spring tracks taps and reset', async ({ app, screen }) => {
await app.open();
await expect(screen.getByTestId('home-screen')).toBeVisible({
timeout: 30_000,
});

const entry = screen.getByTestId('home-reanimated-button');
await screen.scrollUntilVisible(entry);
await entry.tap();
await expect(screen.getByTestId('reanimated-screen')).toBeVisible();

const width = screen.getByTestId('reanimated-width-report');
await expect(width).toHaveText('width: 100');

await screen.getByTestId('reanimated-click-button').tap();
await expect(width).toHaveText('width: 150');

await screen.getByTestId('reanimated-click-button').tap();
await expect(width).toHaveText('width: 200');

await screen.getByTestId('reanimated-reset-button').tap();
await expect(width).toHaveText('width: 100');
});
32 changes: 32 additions & 0 deletions apps/tester-federation-v2/e2e/remote-chunk.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { test } from '@e2e-dev/mobile';
import { expect } from 'e2e';

test('prefetch, load, and invalidate the host remote chunk', async ({
app,
screen,
}) => {
await app.open();
await expect(screen.getByTestId('home-screen')).toBeVisible({
timeout: 30_000,
});

const entry = screen.getByTestId('home-remote-chunk-button');
await screen.scrollUntilVisible(entry);
await entry.tap();
await expect(screen.getByTestId('remote-chunk-screen')).toBeVisible();

await screen.getByTestId('remote-chunk-prefetch-button').tap();
await expect(
screen.getByTestId('remote-chunk-prefetch-button')
).toBeDisabled();

await screen.getByTestId('remote-chunk-load-button').tap();
await expect(screen.getByTestId('remote-chunk-content')).toBeVisible();

await screen.getByTestId('remote-chunk-invalidate-button').tap();
await expect(screen.getByTestId('remote-chunk-content')).toBeHidden();
await expect(screen.getByTestId('remote-chunk-load-button')).toBeVisible();
await expect(
screen.getByTestId('remote-chunk-prefetch-button')
).toBeEnabled();
});
Loading