Skip to content

test(remix): Add a boilerplate for Remix SDK integration tests. #5453

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 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
35 changes: 35 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -571,3 +571,38 @@ jobs:
run: |
cd packages/node-integration-tests
yarn test

job_remix_integration_tests:
name: Remix SDK Integration Tests (${{ matrix.node }})
needs: [job_get_metadata, job_build]
runs-on: ubuntu-latest
timeout-minutes: 10
continue-on-error: true
strategy:
matrix:
node: [14, 16, 18]
steps:
- name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
uses: actions/checkout@v2
with:
ref: ${{ env.HEAD_COMMIT }}
- name: Set up Node
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- name: Check dependency cache
uses: actions/cache@v2
with:
path: ${{ env.CACHED_DEPENDENCY_PATHS }}
key: ${{ needs.job_build.outputs.dependency_cache_key }}
- name: Check build cache
uses: actions/cache@v2
with:
path: ${{ env.CACHED_BUILD_PATHS }}
key: ${{ env.BUILD_CACHE_KEY }}
- name: Run integration tests
env:
NODE_VERSION: ${{ matrix.node }}
run: |
cd packages/remix
yarn test:integration:ci
1 change: 1 addition & 0 deletions packages/remix/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = {
parserOptions: {
jsx: true,
},
ignorePatterns: ['playwright.config.ts', 'test/integration/**'],
extends: ['../../.eslintrc.js'],
rules: {
'@sentry-internal/sdk/no-async-await': 'off',
Expand Down
7 changes: 6 additions & 1 deletion packages/remix/jest.config.js
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
module.exports = require('../../jest/jest.config.js');
const baseConfig = require('../../jest/jest.config.js');

module.exports = {
...baseConfig,
testPathIgnorePatterns: ['<rootDir>/build/', '<rootDir>/node_modules/', '<rootDir>/test/integration/'],
};
6 changes: 6 additions & 0 deletions packages/remix/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@
"lint:eslint": "eslint . --cache --cache-location '../../eslintcache/' --format stylish",
"lint:prettier": "prettier --check \"{src,test,scripts}/**/*.ts\"",
"test": "run-s test:unit",
"test:integration": "run-s test:integration:prepare test:integration:client test:integration:server",
"test:integration:ci": "run-s test:integration:prepare test:integration:client:ci test:integration:server",
"test:integration:prepare": "(cd test/integration && yarn)",
"test:integration:client": "yarn playwright install-deps && yarn playwright test test/integration/test/client/",
"test:integration:client:ci": "yarn test:integration:client --browser='all' --reporter='line'",
"test:integration:server": "jest --config=test/integration/jest.config.js test/integration/test/server/",
"test:unit": "jest",
"test:watch": "jest --watch"
},
Expand Down
16 changes: 16 additions & 0 deletions packages/remix/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { PlaywrightTestConfig } from '@playwright/test';

const config: PlaywrightTestConfig = {
retries: 2,
timeout: 12000,
use: {
baseURL: 'http://localhost:3000',
},
workers: 3,
webServer: {
command: '(cd test/integration/ && yarn build && yarn start)',
port: 3000,
},
};

export default config;
10 changes: 10 additions & 0 deletions packages/remix/test/integration/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules

/.cache
/build
/public/build
.env
/test-results/
/playwright-report/
/playwright/.cache/
yarn.lock
16 changes: 16 additions & 0 deletions packages/remix/test/integration/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { RemixBrowser, useLocation, useMatches } from '@remix-run/react';
import { hydrate } from 'react-dom';
import * as Sentry from '@sentry/remix';
import { useEffect } from 'react';

Sentry.init({
dsn: 'https://[email protected]/1337',
tracesSampleRate: 1,
integrations: [
new Sentry.BrowserTracing({
routingInstrumentation: Sentry.remixRouterInstrumentation(useEffect, useLocation, useMatches),
}),
],
});

hydrate(<RemixBrowser />, document);
25 changes: 25 additions & 0 deletions packages/remix/test/integration/app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { EntryContext } from '@remix-run/node';
import { RemixServer } from '@remix-run/react';
import { renderToString } from 'react-dom/server';
import * as Sentry from '@sentry/remix';

Sentry.init({
dsn: 'https://[email protected]/1337',
tracesSampleRate: 1,
});

export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
let markup = renderToString(<RemixServer context={remixContext} url={request.url} />);

responseHeaders.set('Content-Type', 'text/html');

return new Response('<!DOCTYPE html>' + markup, {
status: responseStatusCode,
headers: responseHeaders,
});
}
28 changes: 28 additions & 0 deletions packages/remix/test/integration/app/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { MetaFunction } from '@remix-run/node';
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react';
import { withSentry } from '@sentry/remix';

export const meta: MetaFunction = () => ({
charset: 'utf-8',
title: 'New Remix App',
viewport: 'width=device-width,initial-scale=1',
});

function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}

export default withSentry(App);
7 changes: 7 additions & 0 deletions packages/remix/test/integration/app/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Index() {
return (
<div>
<h1>Remix Integration Tests Home</h1>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { json, LoaderFunction } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';

type LoaderData = { id: string };

export const loader: LoaderFunction = async ({ params: { id } }) => {
return json({
id,
});
};

export default function LoaderJSONResponse() {
const data = useLoaderData<LoaderData>();

return (
<div>
<h1>{data.id}</h1>
</div>
);
}
9 changes: 9 additions & 0 deletions packages/remix/test/integration/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const baseConfig = require('../../jest.config.js');

module.exports = {
...baseConfig,
testMatch: [`${__dirname}/test/server/**/*.test.ts`],
testPathIgnorePatterns: [`${__dirname}/test/client`],
detectOpenHandles: true,
forceExit: true,
};
38 changes: 38 additions & 0 deletions packages/remix/test/integration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"private": true,
"sideEffects": false,
"scripts": {
"build": "remix build",
"dev": "remix dev",
"start": "remix-serve build"
},
"dependencies": {
"@remix-run/express": "^1.6.5",
"@remix-run/node": "^1.6.5",
"@remix-run/react": "^1.6.5",
"@remix-run/serve": "^1.6.5",
"@sentry/remix": "file:../..",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@remix-run/dev": "^1.6.5",
"@types/react": "^17.0.47",
"@types/react-dom": "^17.0.17",
"typescript": "^4.2.4"
},
"resolutions": {
"@sentry/browser": "file:../../../browser",
"@sentry/core": "file:../../../core",
"@sentry/hub": "file:../../../hub",
"@sentry/integrations": "file:../../../integrations",
"@sentry/node": "file:../../../node",
"@sentry/react": "file:../../../react",
"@sentry/tracing": "file:../../../tracing",
"@sentry/types": "file:../../../types",
"@sentry/utils": "file:../../../utils"
},
"engines": {
"node": ">=14"
}
}
7 changes: 7 additions & 0 deletions packages/remix/test/integration/remix.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
appDirectory: 'app',
assetsBuildDirectory: 'public/build',
serverBuildPath: 'build/index.js',
publicPath: '/build/',
};
12 changes: 12 additions & 0 deletions packages/remix/test/integration/test/client/pageload.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getFirstSentryEnvelopeRequest } from './utils/helpers';
import { test, expect } from '@playwright/test';
import { Event } from '@sentry/types';

test('should add `pageload` transaction on load.', async ({ page }) => {
const envelope = await getFirstSentryEnvelopeRequest<Event>(page, '/');

expect(envelope.contexts?.trace.op).toBe('pageload');
expect(envelope.tags?.['routing.instrumentation']).toBe('remix-router');
expect(envelope.type).toBe('transaction');
expect(envelope.transaction).toBe('routes/index');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../../../../../../integration-tests/utils/helpers';
23 changes: 23 additions & 0 deletions packages/remix/test/integration/test/server/loader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { assertSentryTransaction, getEnvelopeRequest, runServer } from './utils/helpers';

describe('Remix API Loaders', () => {
it('correctly instruments a Remix API loader', async () => {
const baseURL = await runServer();
const url = `${baseURL}/loader-json-response/123123`;
const envelope = await getEnvelopeRequest(url);
const transaction = envelope[2];

assertSentryTransaction(transaction, {
spans: [
{
description: url,
op: 'remix.server.loader',
},
{
description: url,
op: 'remix.server.documentRequest',
},
],
});
});
});
24 changes: 24 additions & 0 deletions packages/remix/test/integration/test/server/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import express from 'express';
import { createRequestHandler } from '@remix-run/express';
import { getPortPromise } from 'portfinder';

export * from '../../../../../../node-integration-tests/utils';

/**
* Runs a test server
* @returns URL
*/
export async function runServer(): Promise<string> {
const app = express();
const port = await getPortPromise();

app.all('*', createRequestHandler({ build: require('../../../build') }));

const server = app.listen(port, () => {
setTimeout(() => {
server.close();
}, 4000);
});

return `http://localhost:${port}`;
}
20 changes: 20 additions & 0 deletions packages/remix/test/integration/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2019"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"resolveJsonModule": true,
"target": "ES2019",
"strict": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
},
"noEmit": true
}
}
9 changes: 9 additions & 0 deletions packages/remix/test/integration/tsconfig.test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",

"include": ["test/**/*"],

"compilerOptions": {
"types": ["node", "jest"]
}
}
3 changes: 2 additions & 1 deletion packages/remix/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"include": ["test/**/*"],

"compilerOptions": {
"types": ["node", "jest"]
"types": ["node", "jest"],
"esModuleInterop": true
}
}