Use Playwright to test demos (#106)

* Run tests and demos with playwright
* ci: renamed workflow
* Improved demo readmes
* Use a separate page for each test
This commit is contained in:
Hendrik Eeckhaut
2025-05-28 08:50:05 +02:00
committed by GitHub
parent 8bc8a94948
commit 4cecbb5334
37 changed files with 933 additions and 1553 deletions

View File

@@ -5,12 +5,6 @@ on:
release: release:
types: [published] types: [published]
env:
LOCAL-NOTARY: true
LOCAL-WS: false
HEADLESS: true
PUPPETEER_SKIP_DOWNLOAD: true
jobs: jobs:
build-and-test: build-and-test:
name: Build and test name: Build and test
@@ -37,7 +31,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 20
cache: 'npm' cache: 'npm'
- name: Install stable nightly toolchain - name: Install stable nightly toolchain
@@ -61,27 +55,18 @@ jobs:
- name: Build - name: Build
run: npm run build run: npm run build
- name: Lint - name: Lint
run: npm run lint run: npm run lint
- name: Install Chrome
uses: browser-actions/setup-chrome@v1
id: setup-chrome
with:
chrome-version: 121.0.6167.85
- name: Set CHROME_PATH environment variable - name: install wstcp
run: echo "CHROME_PATH=${{ steps.setup-chrome.outputs['chrome-path'] }}" >> $GITHUB_ENV run: cargo install wstcp
- name: Install Chromium (Playwright)
run: npx playwright install --with-deps chromium
- name: Test - name: Test
run: | run: npm run test
# Install wstcp and use it in the background
cargo install wstcp
wstcp --bind-addr 127.0.0.1:55688 raw.githubusercontent.com:443 &
WSTCP_PID=$!
trap "kill $WSTCP_PID" EXIT
npm run test
- name: Determine release type (dry-run or publish) - name: Determine release type (dry-run or publish)
run: | run: |

54
.github/workflows/playwright.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: Tests demos
on:
pull_request:
jobs:
test:
timeout-minutes: 60
name: Tests demos
runs-on: ubuntu-latest
services:
notary-server:
image: ghcr.io/tlsnotary/tlsn/notary-server:v0.1.0-alpha.10
env:
NOTARY_SERVER__TLS__ENABLED: false
ports:
- 7047:7047
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: build tlsn-js
run: npm ci; npm run build
- name: install wstcp
run: cargo install wstcp
- name: Install Chromium (Playwright)
run: npx playwright install --with-deps chromium
- name: Test react demo
working-directory: demo/react-ts-webpack
continue-on-error: true
run: |
set -e
npm i
npm run test
- name: Test interactive verifier demo
continue-on-error: true
run: |
set -e
cd demo/interactive-demo/verifier-rs
cargo build --release
cd ../prover-ts
npm i
npm run test
- name: Test web-to-web p2p demo
working-directory: demo/react-ts-webpack
continue-on-error: true
run: |
set -e
npm run test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: '**/playwright-report/'
retention-days: 30

8
.gitignore vendored
View File

@@ -6,4 +6,10 @@ dev-build/
test-build/ test-build/
./demo/node_modules ./demo/node_modules
utils/tlsn utils/tlsn
.vscode .vscode
# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -1,38 +1,59 @@
# Test Rust Prover # Interactive Verifier Demo
1. Start the verifier: This demo shows how to use TLSNotary **without a notary**: a direct proof between a prover and a verifier, where the verifier checks both the TLS session and the revealed data.
```bash
cd verifier-rs; cargo run --release
```
2. Run the prover:
```bash
cd prover-rs; cargo run --release
```
# Test Browser Prover There are two prover implementations:
1. Start the verifier: - **Rust**
```bash - **TypeScript** (browser)
cd verifier-rs; cargo run --release The verifier is implemented in Rust.
```
2. Since a web browser doesn't have the ability to make TCP connection, we need to use a websocket proxy server to access <raw.githubusercontent.com>.
```bash
cargo install wstcp
wstcp --bind-addr 127.0.0.1:55688 raw.githubusercontent.com:443 ---
```
3. Run the prover ## Interactive Verifier Demo with Rust Prover
1. Build tlsn-js
1. **Start the verifier:**
```bash
cd verifier-rs
cargo run --release
```
2. **Run the prover:**
```bash
cd prover-rs
cargo run --release
```
---
## Interactive Verifier Demo with TypeScript Prover (Browser)
1. **Start the verifier:**
```bash
cd verifier-rs
cargo run --release
```
2. **Set up a websocket proxy for raw.githubusercontent.com**
Browsers cannot make raw TCP connections, so a websocket proxy is required:
```bash
cargo install wstcp
wstcp --bind-addr 127.0.0.1:55688 raw.githubusercontent.com:443
```
3. **Run the prover in the browser:**
1. **Build tlsn-js**
```bash ```bash
cd .. cd ..
npm i npm install
npm run build npm run build
npm link
``` ```
2. Build demo prover-ts 2. **Build and start the TypeScript prover demo**
```bash ```bash
cd prover-ts cd prover-ts
npm i npm install
npm link
npm run dev npm run dev
``` ```
3. Open <http://localhost:3456/> and click **Start Prover** 3. **Open the demo in your browser:**
Go to [http://localhost:8080/](http://localhost:8080/) and click **Start Prover**.
---
**Tip:**
If you encounter issues, make sure all dependencies are installed and the websocket proxy is running before starting the browser demo.

View File

@@ -1 +1,8 @@
package-lock.json package-lock.json
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React/Typescript Example</title> <title>TLSNotary React TypeScript Demo</title>
</head> </head>
<body> <body>

View File

@@ -4,7 +4,9 @@
"description": "", "description": "",
"main": "webpack.js", "main": "webpack.js",
"scripts": { "scripts": {
"dev": "webpack-dev-server --config webpack.js" "dev": "webpack-dev-server --config webpack.js",
"start": "webpack serve --config webpack.js",
"test": "npx playwright test"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",

View File

@@ -0,0 +1,90 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:8080',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: [
{
command: 'npm run start',
url: 'http://localhost:8080',
reuseExistingServer: !process.env.CI,
},
{
command: 'wstcp --bind-addr 127.0.0.1:55688 raw.githubusercontent.com:443',
reuseExistingServer: true,
},
{
command: 'cargo run --release',
cwd: '../verifier-rs',
reuseExistingServer: true,
}
]
});

View File

@@ -224,7 +224,7 @@ function App(): ReactElement {
</div> </div>
) : ( ) : (
<div className="bg-gray-100 border border-gray-300 p-4 rounded-lg mt-4"> <div className="bg-gray-100 border border-gray-300 p-4 rounded-lg mt-4">
<pre className="text-left text-sm text-gray-800 whitespace-pre-wrap overflow-auto"> <pre data-testid="proof-data" className="text-left text-sm text-gray-800 whitespace-pre-wrap overflow-auto">
{JSON.stringify(result, null, 2)} {JSON.stringify(result, null, 2)}
</pre> </pre>
</div> </div>

View File

@@ -0,0 +1,16 @@
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/TLSNotary/)
});
test('run demo', async ({ page }) => {
await page.goto('/');
// Click the get started link.
await page.getByRole('button', { name: 'Start Prover' }).click();
await expect(page.getByTestId('proof-data')).toContainText('Unredacted data successfully revealed to Verifier', { timeout: 60000 });
});

View File

@@ -1 +1,8 @@
package-lock.json package-lock.json
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -0,0 +1,79 @@
# TLSNotary in React/TypeScript with `tlsn-js`
This demo shows how to use TLSNotary with a delegated verifier, also known as a **notary**.
In this demo, we request JSON data from a GitHub page, use `tlsn-js` to notarize the TLS request with TLSNotary, and display the attestation and revealed data.
> **Note:**
> This demo uses TLSNotary to notarize **public** data for simplicity. In real-world applications, TLSNotary is especially valuable for notarizing private and sensitive data.
---
## Setup
Before running the demo, you need to start a local notary server and a websocket proxy. If you prefer to use the hosted test servers from PSE, see the section below.
### Websocket Proxy
Browsers cannot make raw TCP connections, so a websocket proxy server is required.
1. **Install [wstcp](https://github.com/sile/wstcp):**
```sh
cargo install wstcp
```
2. **Run a websocket proxy for `https://raw.githubusercontent.com`:**
```sh
wstcp --bind-addr 127.0.0.1:55688 raw.githubusercontent.com:443
```
> Note: The `raw.githubusercontent.com:443` argument specifies the server used in this quick start.
### Run a Local Notary Server
You also need to run a local notary server for this demo.
- **Using Git and Rust Cargo:**
```sh
git clone https://github.com/tlsnotary/tlsn.git
cargo run --release --bin notary-server
```
- **Using Docker (from the root of the tlsn-js repo):**
```sh
npm run notary
```
The notary server will now be running in the background, waiting for connections.
---
### Use the PSE Web Proxy and Notary
If you want to use the hosted PSE notary and proxy:
1. Open `app.tsx` in your editor.
2. Replace the notary URL:
```ts
notaryUrl: 'https://notary.pse.dev/v0.1.0-alpha.10',
```
This uses the [PSE](https://pse.dev) notary server to notarize the API request. You can use a different or [local notary](#run-a-local-notary-server); a local server will be faster due to the high bandwidth and low network latency.
3. Replace the websocket proxy URL:
```ts
websocketProxyUrl: 'wss://notary.pse.dev/proxy?token=raw.githubusercontent.com',
```
This uses a proxy hosted by [PSE](https://pse.dev). You can use a different or local proxy if you prefer.
---
## Run the Demo
1. **Install dependencies:**
```sh
npm i
```
2. **Start the Webpack Dev Server:**
```sh
npm run dev
```
3. **Open the demo in your browser:**
Go to [http://localhost:8080](http://localhost:8080)
4. **Click the "Start demo" button**
5. **Open Developer Tools** and monitor the console logs
œœ

View File

@@ -4,13 +4,13 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React/Typescrip Example</title> <title>TLSNotary React TypeScript Demo</title>
</head> </head>
<body> <body>
<script> <script>
</script> </script>
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>

View File

@@ -5,16 +5,18 @@
"main": "webpack.js", "main": "webpack.js",
"scripts": { "scripts": {
"dev": "webpack-dev-server --config webpack.js", "dev": "webpack-dev-server --config webpack.js",
"build": "webpack --config webpack.js" "build": "webpack --config webpack.js",
"start": "webpack serve --config webpack.js",
"test": "npx playwright test"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"comlink": "^4.4.1", "comlink": "^4.4.1",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"http-parser-js": "^0.5.9",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
"http-parser-js": "^0.5.9",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-loader-spinner": "^6.1.6", "react-loader-spinner": "^6.1.6",
@@ -25,6 +27,8 @@
"tlsn-js": "../../" "tlsn-js": "../../"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.52.0",
"@types/node": "^22.15.18",
"@types/react": "^18.0.26", "@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10", "@types/react-dom": "^18.0.10",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
@@ -41,4 +45,4 @@
"webpack-cli": "^4.10.0", "webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1" "webpack-dev-server": "^4.11.1"
} }
} }

View File

@@ -0,0 +1,85 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:8080',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: [
{
command: 'npm run start',
url: 'http://localhost:8080',
reuseExistingServer: !process.env.CI,
},
{
command: 'wstcp --bind-addr 127.0.0.1:55688 raw.githubusercontent.com:443',
reuseExistingServer: true,
}
]
});

View File

@@ -140,6 +140,7 @@ function App(): ReactElement {
const proof = await (Prover.notarize as typeof TProver.notarize)({ const proof = await (Prover.notarize as typeof TProver.notarize)({
notaryUrl: notaryUrl, notaryUrl: notaryUrl,
websocketProxyUrl: websocketProxyUrl, websocketProxyUrl: websocketProxyUrl,
maxRecvData: 2048,
url: serverUrl, url: serverUrl,
method: 'GET', method: 'GET',
headers: { headers: {
@@ -285,7 +286,7 @@ function App(): ReactElement {
<summary className="cursor-pointer text-slate-600"> <summary className="cursor-pointer text-slate-600">
View Proof View Proof
</summary> </summary>
<pre <pre data-testid="proof-data"
className="mt-2 p-2 bg-slate-100 rounded text-sm text-slate-800 overflow-auto" className="mt-2 p-2 bg-slate-100 rounded text-sm text-slate-800 overflow-auto"
style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-all' }} style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}
> >
@@ -301,7 +302,7 @@ function App(): ReactElement {
) : !result ? ( ) : !result ? (
<i className="text-slate-500">verifying</i> <i className="text-slate-500">verifying</i>
) : ( ) : (
<pre <pre data-testid="verify-data"
className="mt-2 p-2 bg-slate-100 rounded text-sm text-slate-800 overflow-auto" className="mt-2 p-2 bg-slate-100 rounded text-sm text-slate-800 overflow-auto"
style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-all' }} style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}
> >

View File

@@ -0,0 +1,36 @@
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/TLSNotary React TypeScript Demo/)
});
test('run demo (normal)', async ({ page }) => {
test.setTimeout(60000);
await page.goto('/');
// Click the get started link.
await page.getByRole('button', { name: 'Start Demo (Normal config)' }).click();
await expect(page.getByTestId('proof-data')).toContainText('"data":', { timeout: 60000 });
let verify_data = await page.getByTestId('verify-data').innerText();
expect(verify_data).toContain('"serverName": "raw.githubusercontent.com"');
expect(verify_data).toContain('John Doe');
});
test('run demo (helper)', async ({ page }) => {
test.setTimeout(60000);
await page.goto('/');
// Click the get started link.
await page.getByRole('button', { name: 'Start Demo 2 (With helper method)' }).click();
await expect(page.getByTestId('proof-data')).toContainText('"data":', { timeout: 60000 });
// await page.screenshot({ path: 'screenshot.png', fullPage: true });
let verify_data = await page.getByTestId('verify-data').innerText();
expect(verify_data).toContain('"serverName": "raw.githubusercontent.com"');
expect(verify_data).toContain('"recv"');
});

View File

@@ -1 +1,8 @@
package-lock.json package-lock.json
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -12,7 +12,7 @@ In this demo, the two web clients run in the same browser page (`./src/app.tsx`)
npm i npm i
npm run dev npm run dev
``` ```
2. Open <http://localhost:3456/> 2. Open <http://localhost:8080/>
3. Click the **Start Demo** button 3. Click the **Start Demo** button
The Prover window logs the Prover's output, the Verifier logs the Verifier's output. In the console view you can see the websocket log. The Prover window logs the Prover's output, the Verifier logs the Verifier's output. In the console view you can see the websocket log.

View File

@@ -4,13 +4,13 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React/Typescrip Example</title> <title>Web-to-Web P2P Demo</title>
</head> </head>
<body> <body>
<script> <script>
</script> </script>
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>

View File

@@ -7,7 +7,9 @@
"dev:server": "node ./server/index.js", "dev:server": "node ./server/index.js",
"dev:ui": "webpack-dev-server --config webpack.js", "dev:ui": "webpack-dev-server --config webpack.js",
"dev": "concurrently npm:dev:ui npm:dev:server", "dev": "concurrently npm:dev:ui npm:dev:server",
"build": "webpack --config webpack.js" "build": "webpack --config webpack.js",
"start:ui": "webpack serve --config webpack.js",
"test": "npm run build && npx playwright test"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
@@ -45,4 +47,4 @@
"webpack-cli": "^4.10.0", "webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1" "webpack-dev-server": "^4.11.1"
} }
} }

View File

@@ -0,0 +1,90 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:8080',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: [
{
command: 'npm run start:ui',
url: 'http://localhost:8080',
reuseExistingServer: !process.env.CI,
},
{
command: 'wstcp --bind-addr 127.0.0.1:55688 raw.githubusercontent.com:443',
reuseExistingServer: true,
},
{
command: 'node ./server/index.js',
port: 3001,
reuseExistingServer: !process.env.CI,
}
]
});

View File

@@ -243,6 +243,7 @@ function App(): ReactElement {
{proverMessages.map((m, index) => ( {proverMessages.map((m, index) => (
<span <span
key={index} key={index}
data-testid="prover-data"
className="px-3 py-1 text-slate-600 break-all" className="px-3 py-1 text-slate-600 break-all"
> >
{m} {m}
@@ -257,6 +258,7 @@ function App(): ReactElement {
{verifierMessages.map((m, index) => ( {verifierMessages.map((m, index) => (
<span <span
key={index} key={index}
data-testid="verifier-data"
className="px-3 py-1 text-slate-600 break-all" className="px-3 py-1 text-slate-600 break-all"
> >
{m} {m}
@@ -271,7 +273,7 @@ function App(): ReactElement {
disabled={!ready || started} disabled={!ready || started}
onClick={start} onClick={start}
> >
<div className="flex items-center"> <div data-testid="start" className="flex items-center">
{ready && !started ? ( {ready && !started ? (
<>Start Demo</> <>Start Demo</>
) : ( ) : (

View File

@@ -0,0 +1,24 @@
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/Web-to-Web P2P Demo/)
});
test('run web-to-web p2p demo', async ({ page }) => {
await page.goto('/');
await page.getByTestId('start').click();
await expect(page.getByTestId('start')).toContainText('Start Demo', { timeout: 60000 });
const proverMessages = await page.getByTestId('prover-data').allTextContents();
expect(proverMessages.some(text => text.includes('Transcript received'))).toBe(true);
// console.log('Verifier Messages:', proverMessages);
expect(proverMessages.some(text => text.includes('"name": "John Doe",'))).toBe(true);
expect(proverMessages.some(text => text.includes('"address": {'))).toBe(true);
const verifierMessages = await page.getByTestId('verifier-data').allTextContents();
expect(verifierMessages.some(text => text.includes('Verification completed'))).toBe(true);
expect(verifierMessages.some(text => text.includes('***"name": "John Doe"*************************"street": "123 Elm Street"***'))).toBe(true);
});

1237
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,13 +23,12 @@
"lint:eslint": "eslint . --fix", "lint:eslint": "eslint . --fix",
"lint:tsc": "tsc --noEmit", "lint:tsc": "tsc --noEmit",
"lint": "concurrently npm:lint:tsc npm:lint:eslint", "lint": "concurrently npm:lint:tsc npm:lint:eslint",
"run:spec": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha -r ts-node/register 'test/specs/*.ts'", "test": "playwright test",
"run:e2e": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha -r ts-node/register 'test/testRunner.ts'",
"test": "npm run build:test && npm run run:e2e",
"notary": "docker run --platform=linux/amd64 -p 7047:7047 --rm ghcr.io/tlsnotary/tlsn/notary-server:v0.1.0-alpha.10 notary-server --tls-enabled=false" "notary": "docker run --platform=linux/amd64 -p 7047:7047 --rm ghcr.io/tlsnotary/tlsn/notary-server:v0.1.0-alpha.10 notary-server --tls-enabled=false"
}, },
"devDependencies": { "devDependencies": {
"@types/mocha": "^10.0.6", "@playwright/test": "^1.52.0",
"@types/node": "^22.15.18",
"@types/serve-handler": "^6.1.4", "@types/serve-handler": "^6.1.4",
"browserify": "^17.0.0", "browserify": "^17.0.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
@@ -43,20 +42,17 @@
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"file-loader": "^5.0.2", "file-loader": "^5.0.2",
"html-webpack-plugin": "~5.3.2", "html-webpack-plugin": "~5.3.2",
"https-browserify": "^1.0.0",
"http-parser-js": "^0.5.9", "http-parser-js": "^0.5.9",
"https-browserify": "^1.0.0",
"image-webpack-loader": "^6.0.0", "image-webpack-loader": "^6.0.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"mocha": "^10.2.0",
"node-loader": "^0.6.0", "node-loader": "^0.6.0",
"prettier": "^3.0.2", "prettier": "^3.0.2",
"process": "^0.11.10", "process": "^0.11.10",
"puppeteer": "^24.1.0",
"serve": "14.2.1", "serve": "14.2.1",
"serve-handler": "^6.1.5", "serve-handler": "^6.1.5",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"ts-loader": "^6.2.1", "ts-loader": "^6.2.1",
"ts-mocha": "^10.0.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"typescript-eslint": "^7.4.0", "typescript-eslint": "^7.4.0",

View File

@@ -0,0 +1,26 @@
import { test, expect } from '@playwright/test';
test('full-integration', async ({ page }) => {
// log browser console messages
page.on('console', (msg) => {
console.log(`[BROWSER ${msg.type().toUpperCase()}] ${msg.text()}`);
});
await page.goto('/full-integration');
await expect(page.getByTestId('full-integration')).toHaveText(/\{.*\}/s, { timeout: 60000 });
const json = await page.getByTestId('full-integration').innerText();
const { sent, recv, server_name, version, meta } = JSON.parse(json);
expect(version).toBe('0.1.0-alpha.10');
expect(new URL(meta.notaryUrl!).protocol === 'http:');
expect(server_name).toBe('raw.githubusercontent.com');
expect(sent).toContain('host: raw.githubusercontent.com');
expect(sent).not.toContain('secret: test_secret');
expect(recv).toContain('"id": 1234567890');
expect(recv).toContain('"city": "Anytown"');
expect(recv).toContain('"postalCode": "12345"');
});

View File

@@ -0,0 +1,20 @@
import { test, expect } from '@playwright/test';
test('simple verify', async ({ page }) => {
// log browser console messages
page.on('console', (msg) => {
console.log(`[BROWSER ${msg.type().toUpperCase()}] ${msg.text()}`);
});
await page.goto('/simple-verify');
await expect(page.getByTestId('simple-verify')).toHaveText(/\{.*\}/s);
const json = await page.getByTestId('simple-verify').innerText();
const { sent, recv } = JSON.parse(json);
expect(sent).toContain('host: raw.githubusercontent.com');
expect(recv).toContain('"name": "John Doe"');
expect(recv).toContain('"city": "Anytown"');
expect(recv).toContain('"id": **********,');
});

85
playwright.config.ts Normal file
View File

@@ -0,0 +1,85 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './playwright-test',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:3001',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: [
{
command: 'npm run build:test && npm run serve:test',
url: 'http://localhost:3001',
reuseExistingServer: !process.env.CI,
},
{
command: 'wstcp --bind-addr 127.0.0.1:55688 raw.githubusercontent.com:443',
reuseExistingServer: true,
},
]
});

111
readme.md
View File

@@ -1,43 +1,42 @@
![MIT licensed][mit-badge] ![MIT licensed][mit-badge]
![Apache licensed][apache-badge] ![Apache licensed][apache-badge]
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[apache-badge]: https://img.shields.io/github/license/saltstack/salt [apache-badge]: https://img.shields.io/github/license/saltstack/salt
# tlsn-js # tlsn-js
NPM Modules for proving and verifying using TLSNotary in the browser. NPM modules for proving and verifying using TLSNotary in the browser.
The prover requires a [notary-server](https://github.com/tlsnotary/notary-server) and a websocket proxy.
> [!IMPORTANT] > [!IMPORTANT]
> The primary purpose of `tlsn-js` is to support the development of the [TLSNotary browser extension](https://github.com/tlsnotary/tlsn-extension/). > `tlsn-js` is developed specifically for **browser environments** and does **not** work in Node.js.
> [!IMPORTANT]
> The primary goal of `tlsn-js` is to support the development of the [TLSNotary browser extension](https://github.com/tlsnotary/tlsn-extension/).
> **Please do not treat this as a public API (yet).** > **Please do not treat this as a public API (yet).**
> [!IMPORTANT]
> `tlsn-js` is developed for the usage of TLSNotary **in the Browser**. This module does not work in `nodejs`.
## License ## License
This repository is licensed under either of
This repository is licensed under either:
- [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
- [MIT license](http://opensource.org/licenses/MIT) - [MIT License](http://opensource.org/licenses/MIT)
at your option. ...at your option.
## Examples ## Examples
`tlsn-js` can be used in many different modes, depending on your use case. `tlsn-js` can be used in several modes depending on your use case.
The `./demo` folder contains three demos of `tlsn-js`: The `./demo` folder contains three demos:
* `react-ts-webpack`: create an attestation with a Notary and render the result. - `react-ts-webpack`: Create an attestation with a Notary and render the result.
* `interactive-demo`: prove data interactively to a Verifier. - `interactive-demo`: Prove data interactively to a Verifier.
* `web-to-web-p2p`: prove data between two peers, in the browser. - `web-to-web-p2p`: Prove data between two browser peers.
## Running a local websocket proxy ## Running a Local WebSocket Proxy
In the demos, we attest data from `https://raw.githubusercontent.com`. Because the browser does not allow for TCP connections, you need to set up a websocket proxy: In the demos, we attest data from `https://raw.githubusercontent.com`. Since browsers do not support raw TCP connections, a WebSocket proxy is required:
1. Install [wstcp](https://github.com/sile/wstcp): 1. Install [wstcp](https://github.com/sile/wstcp):
@@ -47,33 +46,42 @@ In the demos, we attest data from `https://raw.githubusercontent.com`. Because t
| brew | `brew install wstcp` | | brew | `brew install wstcp` |
| source | https://github.com/sile/wstcp | | source | https://github.com/sile/wstcp |
2. Run a websocket proxy for `https://raw.githubusercontent.com`: 2. Run a WebSocket proxy for `https://raw.githubusercontent.com`:
```sh
wstcp --bind-addr 127.0.0.1:55688 raw.githubusercontent.com:443 ```sh
``` wstcp --bind-addr 127.0.0.1:55688 raw.githubusercontent.com:443
```
## Install as NPM Package ## Install as NPM Package
```sh ```sh
npm install tlsn-js npm install tlsn-js
``` ```
# Development ## Development
This library is a JS wrapper for `tlsn-wasm`. This library wraps the `tlsn-wasm` module.
To work on `tlsn-wasm` and `tlsn-js` at the same time, replace the "tlsn-wasm" dependency in `package.json` with: To work on both `tlsn-wasm` and `tlsn-js` locally, update `package.json`:
```json
"tlsn-wasm": "./tlsn-wasm/pkg"
``` ```
"tlsn-wasm": "./tlsn-wasm/pkg"
```
and run `npm run build:wasm` to build `tlsn-wasm` locally.
Next, run: Then build `tlsn-wasm`:
```sh
npm run build:wasm
```
Next:
```sh ```sh
npm install npm install
npm run test npm run test
``` ```
Note: if you want to switch back to a build with the version from npm, make sure to reset/remove `package-lock.json`, or it will keep using the local link. > To switch back to the npm-published version of `tlsn-wasm`, delete or reset `package-lock.json` to remove the local path reference.
## Build for NPM ## Build for NPM
@@ -82,9 +90,42 @@ npm install
npm run build npm run build
``` ```
## Adding a new test ## Testing
1. Create a new `new-test.spec.ts` file in the `test/` directory.
2. Add your spec file to the entry object in `webpack.web.dev.config.js`.
3. Add a new `div` block to `test/test.ejs` like this: `<div>Testing "new-test":<div id="new-test"></div></div>`. The `div` id must be the same as the filename.
Testing is slightly complex due to the need for browser-based workers.
- Tests live in the `test/` directory.
- The `tests/` directory contains a Playwright test runner that opens a Chromium browser and runs the actual test page.
Some tests require a running Notary. You can start one via Docker:
```sh
npm run notary
```
### Adding a New `tlsn-js` Test
1. Create a `new-test.spec.ts` file in the `test/` directory.
2. Add your spec file to the `entry` object in `webpack.web.dev.config.js`.
3. Create a corresponding `new-test.spec.ts` file in the `playwright-test/` directory.
4. Add an `expect()` call for it in `tests/test.spec.ts`.
### Testing the Demos
Playwright is also used to test the demos.
```sh
npm install
npm run test
```
- View tests in the browser:
```sh
npx playwright test --ui
```
- Debug tests:
```sh
npx playwright test --debug
```

View File

@@ -8,7 +8,6 @@ import {
Transcript, Transcript,
} from '../../src/lib'; } from '../../src/lib';
import * as Comlink from 'comlink'; import * as Comlink from 'comlink';
import { assert } from '../utils';
import { HTTPParser } from 'http-parser-js'; import { HTTPParser } from 'http-parser-js';
const { init, Prover, Presentation }: any = Comlink.wrap( const { init, Prover, Presentation }: any = Comlink.wrap(
@@ -89,8 +88,7 @@ const { init, Prover, Presentation }: any = Comlink.wrap(
console.log('presentation:', await presentation.serialize()); console.log('presentation:', await presentation.serialize());
console.timeEnd('prove'); console.timeEnd('prove');
const json = await presentation.json(); const json = await presentation.json();
assert(json.version === '0.1.0-alpha.10');
assert(new URL(json.meta.notaryUrl!).protocol === 'http:');
console.time('verify'); console.time('verify');
const { transcript: partialTranscript, server_name } = const { transcript: partialTranscript, server_name } =
@@ -109,19 +107,17 @@ const { init, Prover, Presentation }: any = Comlink.wrap(
console.log("Sent:", sentStr); console.log("Sent:", sentStr);
console.log("Received:", recvStr); console.log("Received:", recvStr);
assert(sentStr.includes('host: raw.githubusercontent.com'));
assert(!sentStr.includes('secret: test_secret'));
assert(recvStr.includes('"id": 1234567890'));
assert(recvStr.includes('"city": "Anytown"'));
assert(recvStr.includes('"postalCode": "12345"'));
assert(server_name === 'raw.githubusercontent.com');
// @ts-ignore // @ts-ignore
document.getElementById('full-integration').textContent = 'OK'; document.getElementById('full-integration').textContent = JSON.stringify({
sent: sentStr,
recv: recvStr,
version: json.version,
meta: json.meta,
server_name
}, null, 2);
} catch (err) { } catch (err) {
console.log('caught error from wasm'); console.log('caught error from wasm');
console.error(err); console.error(err);
// @ts-ignore // @ts-ignore
document.getElementById('full-integration').textContent = err.message; document.getElementById('full-integration').textContent = err.message;
} }

View File

@@ -2,7 +2,6 @@ import { Presentation as _Presentation } from '../../src/lib';
// import { assert } from '../utils'; // import { assert } from '../utils';
import * as Comlink from 'comlink'; import * as Comlink from 'comlink';
import { Transcript } from '../../src/lib'; import { Transcript } from '../../src/lib';
import { assert } from '../utils';
const { init, Presentation }: any = Comlink.wrap( const { init, Presentation }: any = Comlink.wrap(
// @ts-ignore // @ts-ignore
@@ -26,16 +25,11 @@ const { init, Presentation }: any = Comlink.wrap(
const sent = transcript.sent(); const sent = transcript.sent();
const recv = transcript.recv(); const recv = transcript.recv();
console.log('sent', sent);
console.log('recv', recv);
assert(sent.includes('host: raw.githubusercontent.com'));
assert(recv.includes('"name": "John Doe"'));
assert(recv.includes('"city": "Anytown"'));
// @ts-ignore // @ts-ignore
document.getElementById('simple-verify').textContent = 'OK'; document.getElementById('simple-verify').textContent = JSON.stringify({
sent,
recv
}, null, 2);
} catch (err) { } catch (err) {
console.log('caught error from wasm'); console.log('caught error from wasm');
console.error(err); console.error(err);

View File

@@ -1,90 +0,0 @@
import { describe, it } from 'mocha';
import * as assert from 'assert';
import { Transcript } from '../../src/transcript';
describe('Transcript parsing', () => {
it('should parse transcript correctly', async () => {
const transcript = new Transcript({ sent: swapiSent, recv: swapiRecv });
assert.strictEqual(
Buffer.from(transcript.raw.sent).toString('utf-8'),
'GET https://swapi.dev/api/people/1 HTTP/1.1\r\nconnection: close\r\ncontent-length: 25\r\ncontent-type: application/json\r\nhost: swapi.dev\r\n\r\n{"hello":"world","one":1}',
);
assert.strictEqual(
Buffer.from(transcript.raw.recv).toString('utf-8'),
'HTTP/1.1 200 OK\r\nServer: nginx/1.16.1\r\nDate: Fri, 07 Feb 2025 07:37:11 GMT\r\nContent-Type: application/json\r\nTransfer-Encoding: chunked\r\nConnection: close\r\nVary: Accept, Cookie\r\nX-Frame-Options: SAMEORIGIN\r\nETag: \"ee398610435c328f4d0a4e1b0d2f7bbc\"\r\nAllow: GET, HEAD, OPTIONS\r\nStrict-Transport-Security: max-age=15768000\r\n\r\n287\r\n{\"name\":\"Luke Skywalker\",\"height\":\"172\",\"mass\":\"77\",\"hair_color\":\"blond\",\"skin_color\":\"fair\",\"eye_color\":\"blue\",\"birth_year\":\"19BBY\",\"gender\":\"male\",\"homeworld\":\"https://swapi.dev/api/planets/1/\",\"films\":[\"https://swapi.dev/api/films/1/\",\"https://swapi.dev/api/films/2/\",\"https://swapi.dev/api/films/3/\",\"https://swapi.dev/api/films/6/\"],\"species\":[],\"vehicles\":[\"https://swapi.dev/api/vehicles/14/\",\"https://swapi.dev/api/vehicles/30/\"],\"starships\":[\"https://swapi.dev/api/starships/12/\",\"https://swapi.dev/api/starships/22/\"],\"created\":\"2014-12-09T13:50:51.644000Z\",\"edited\":\"2014-12-20T21:17:56.891000Z\",\"url\":\"https://swapi.dev/api/people/1/\"}\r\n0\r\n\r\n',
);
});
});
const swapiRecv = [
72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75, 13, 10, 83, 101,
114, 118, 101, 114, 58, 32, 110, 103, 105, 110, 120, 47, 49, 46, 49, 54, 46,
49, 13, 10, 68, 97, 116, 101, 58, 32, 70, 114, 105, 44, 32, 48, 55, 32, 70,
101, 98, 32, 50, 48, 50, 53, 32, 48, 55, 58, 51, 55, 58, 49, 49, 32, 71, 77,
84, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 32,
97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110,
13, 10, 84, 114, 97, 110, 115, 102, 101, 114, 45, 69, 110, 99, 111, 100, 105,
110, 103, 58, 32, 99, 104, 117, 110, 107, 101, 100, 13, 10, 67, 111, 110, 110,
101, 99, 116, 105, 111, 110, 58, 32, 99, 108, 111, 115, 101, 13, 10, 86, 97,
114, 121, 58, 32, 65, 99, 99, 101, 112, 116, 44, 32, 67, 111, 111, 107, 105,
101, 13, 10, 88, 45, 70, 114, 97, 109, 101, 45, 79, 112, 116, 105, 111, 110,
115, 58, 32, 83, 65, 77, 69, 79, 82, 73, 71, 73, 78, 13, 10, 69, 84, 97, 103,
58, 32, 34, 101, 101, 51, 57, 56, 54, 49, 48, 52, 51, 53, 99, 51, 50, 56, 102,
52, 100, 48, 97, 52, 101, 49, 98, 48, 100, 50, 102, 55, 98, 98, 99, 34, 13,
10, 65, 108, 108, 111, 119, 58, 32, 71, 69, 84, 44, 32, 72, 69, 65, 68, 44,
32, 79, 80, 84, 73, 79, 78, 83, 13, 10, 83, 116, 114, 105, 99, 116, 45, 84,
114, 97, 110, 115, 112, 111, 114, 116, 45, 83, 101, 99, 117, 114, 105, 116,
121, 58, 32, 109, 97, 120, 45, 97, 103, 101, 61, 49, 53, 55, 54, 56, 48, 48,
48, 13, 10, 13, 10, 50, 56, 55, 13, 10, 123, 34, 110, 97, 109, 101, 34, 58,
34, 76, 117, 107, 101, 32, 83, 107, 121, 119, 97, 108, 107, 101, 114, 34, 44,
34, 104, 101, 105, 103, 104, 116, 34, 58, 34, 49, 55, 50, 34, 44, 34, 109, 97,
115, 115, 34, 58, 34, 55, 55, 34, 44, 34, 104, 97, 105, 114, 95, 99, 111, 108,
111, 114, 34, 58, 34, 98, 108, 111, 110, 100, 34, 44, 34, 115, 107, 105, 110,
95, 99, 111, 108, 111, 114, 34, 58, 34, 102, 97, 105, 114, 34, 44, 34, 101,
121, 101, 95, 99, 111, 108, 111, 114, 34, 58, 34, 98, 108, 117, 101, 34, 44,
34, 98, 105, 114, 116, 104, 95, 121, 101, 97, 114, 34, 58, 34, 49, 57, 66, 66,
89, 34, 44, 34, 103, 101, 110, 100, 101, 114, 34, 58, 34, 109, 97, 108, 101,
34, 44, 34, 104, 111, 109, 101, 119, 111, 114, 108, 100, 34, 58, 34, 104, 116,
116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105, 46, 100, 101, 118, 47, 97,
112, 105, 47, 112, 108, 97, 110, 101, 116, 115, 47, 49, 47, 34, 44, 34, 102,
105, 108, 109, 115, 34, 58, 91, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
109, 115, 47, 49, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
109, 115, 47, 50, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
109, 115, 47, 51, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
109, 115, 47, 54, 47, 34, 93, 44, 34, 115, 112, 101, 99, 105, 101, 115, 34,
58, 91, 93, 44, 34, 118, 101, 104, 105, 99, 108, 101, 115, 34, 58, 91, 34,
104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105, 46, 100, 101,
118, 47, 97, 112, 105, 47, 118, 101, 104, 105, 99, 108, 101, 115, 47, 49, 52,
47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105,
46, 100, 101, 118, 47, 97, 112, 105, 47, 118, 101, 104, 105, 99, 108, 101,
115, 47, 51, 48, 47, 34, 93, 44, 34, 115, 116, 97, 114, 115, 104, 105, 112,
115, 34, 58, 91, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112,
105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 115, 116, 97, 114, 115, 104,
105, 112, 115, 47, 49, 50, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47,
47, 115, 119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 115, 116,
97, 114, 115, 104, 105, 112, 115, 47, 50, 50, 47, 34, 93, 44, 34, 99, 114,
101, 97, 116, 101, 100, 34, 58, 34, 50, 48, 49, 52, 45, 49, 50, 45, 48, 57,
84, 49, 51, 58, 53, 48, 58, 53, 49, 46, 54, 52, 52, 48, 48, 48, 90, 34, 44,
34, 101, 100, 105, 116, 101, 100, 34, 58, 34, 50, 48, 49, 52, 45, 49, 50, 45,
50, 48, 84, 50, 49, 58, 49, 55, 58, 53, 54, 46, 56, 57, 49, 48, 48, 48, 90,
34, 44, 34, 117, 114, 108, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47,
115, 119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 112, 101,
111, 112, 108, 101, 47, 49, 47, 34, 125, 13, 10, 48, 13, 10, 13, 10,
];
const swapiSent = [
71, 69, 84, 32, 104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105,
46, 100, 101, 118, 47, 97, 112, 105, 47, 112, 101, 111, 112, 108, 101, 47, 49,
32, 72, 84, 84, 80, 47, 49, 46, 49, 13, 10, 99, 111, 110, 110, 101, 99, 116,
105, 111, 110, 58, 32, 99, 108, 111, 115, 101, 13, 10, 99, 111, 110, 116, 101,
110, 116, 45, 108, 101, 110, 103, 116, 104, 58, 32, 50, 53, 13, 10, 99, 111,
110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 32, 97, 112, 112, 108,
105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 13, 10, 104, 111,
115, 116, 58, 32, 115, 119, 97, 112, 105, 46, 100, 101, 118, 13, 10, 13, 10,
123, 34, 104, 101, 108, 108, 111, 34, 58, 34, 119, 111, 114, 108, 100, 34, 44,
34, 111, 110, 101, 34, 58, 49, 125,
];

View File

@@ -5,19 +5,21 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="/manifest.json">
<title>tlsn-js development</title> <title>
Testing <%= htmlWebpackPlugin.options.testName || 'test' %>
</title>
</head> </head>
<body> <body>
<script> <script>
global = globalThis //<- this should be enough global = globalThis //<- this should be enough
</script> </script>
<div>Testing "full-integration": <h1>Testing "<%= htmlWebpackPlugin.options.testName || 'unknown' %>":</h1>
<div id="full-integration"></div> <pre>
</div> <div id="<%= htmlWebpackPlugin.options.testName || 'test' %>"
<div>Testing "simple-verify": data-testid="<%= htmlWebpackPlugin.options.testName || 'test' %>">
<div id="simple-verify"></div> </div>
</div> </pre>
</body> </body>
</html> </html>

View File

@@ -1,125 +0,0 @@
import puppeteer, { Browser, LaunchOptions, Page } from 'puppeteer';
import { describe, it, before, after } from 'mocha';
const assert = require('assert');
import { exec, ChildProcess } from 'node:child_process';
import * as fs from 'fs';
import path from 'path';
const timeout = 300000;
// puppeteer options
let opts: LaunchOptions = {
headless: !!process.env.HEADLESS ? true : false,
slowMo: 100,
timeout: timeout,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
};
if (process.env.CHROME_PATH) {
opts = {
...opts,
executablePath: process.env.CHROME_PATH,
};
}
let browser: Browser;
let page: Page;
let server: ChildProcess;
const waitForNotaryServer = async () => {
// wait for the notary server to be ready
while (true) {
try {
const response = await fetch('http://127.0.0.1:7047/info');
if (response.ok) {
return;
}
} catch (error) {
console.error('Waiting for local notary server...', error);
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
};
// expose variables
before(async function () {
server = exec('serve --config ../serve.json ./test-build -l 3001');
await waitForNotaryServer();
browser = await puppeteer.launch(opts);
page = await browser.newPage();
// log browser console messages
page.on('console', (msg) => {
console.log(`[BROWSER ${msg.type().toUpperCase()}] ${msg.text()}`);
});
await page.goto('http://127.0.0.1:3001');
});
// close browser and reset global variables
after(async function () {
console.log('Cleaning up:');
try {
server.kill();
console.log('* Stopped Test Web Server ✅');
if (page) {
await page.close();
}
if (browser) {
await browser.close();
const childProcess = browser.process();
if (childProcess) {
childProcess.kill(9);
}
console.log('* Closed browser ✅');
const tests = this.test?.parent?.suites.flatMap((suite) => suite.tests);
const failed = tests!.some((test) => test.state === 'failed');
console.log('tests', tests);
console.log('failed', failed);
process.exit(failed ? 1 : 0);
}
process.exit(1);
} catch (e) {
console.error(e);
process.exit(1);
}
});
describe('tlsn-js test suite', function () {
fs.readdirSync(path.join(__dirname, 'e2e')).forEach((file) => {
const [id] = file.split('.');
it(`Test ID: ${id}`, async function () {
const content = await check(id);
assert.strictEqual(
content,
'OK',
`Test ID: ${id} - Expected 'OK' but got '${content}'`,
);
});
});
});
async function check(testId: string): Promise<string> {
const startTime = Date.now();
const attemptFetchContent = async (): Promise<string> => {
const content = await page.$eval(
`#${testId}`,
(el: any) => el.textContent || '',
);
if (content) return content;
const elapsedTime = Date.now() - startTime;
if (elapsedTime >= timeout) {
throw new Error(
`Timeout: Failed to retrieve content for '#${testId}' within ${timeout} ms.`,
);
}
await new Promise((resolve) => setTimeout(resolve, 1000));
return attemptFetchContent();
};
return attemptFetchContent();
}

View File

@@ -1,3 +0,0 @@
export function assert(expr: any, msg = 'unknown assertion error') {
if (!Boolean(expr)) throw new Error(msg);
}

View File

@@ -33,14 +33,17 @@ const rules = [
const rendererRules = []; const rendererRules = [];
const entry = {
'full-integration': path.join(__dirname, 'test', 'e2e', 'full-integration.spec.ts'),
'simple-verify': path.join(__dirname, 'test', 'e2e', 'simple-verify.spec.ts'),
// add more entries as needed
};
module.exports = [ module.exports = [
{ {
target: 'web', target: 'web',
mode: isProd ? 'production' : 'development', mode: isProd ? 'production' : 'development',
entry: { entry,
'full-integration.spec': path.join(__dirname, 'test', 'e2e', 'full-integration.spec.ts'),
'simple-verify': path.join(__dirname, 'test', 'e2e', 'simple-verify.spec.ts'),
},
output: { output: {
path: __dirname + '/test-build', path: __dirname + '/test-build',
publicPath: '/', publicPath: '/',
@@ -49,25 +52,6 @@ module.exports = [
devtool: 'source-map', devtool: 'source-map',
resolve: { resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.png', '.svg'], extensions: ['.ts', '.tsx', '.js', '.jsx', '.png', '.svg'],
// modules: [
// path.resolve('./node_modules'),
// path.resolve(__dirname, compilerOptions.baseUrl),
// ],
// fallback: {
// browserify: require.resolve('browserify'),
// stream: require.resolve('stream-browserify'),
// path: require.resolve('path-browserify'),
// crypto: require.resolve('crypto-browserify'),
// os: require.resolve('os-browserify/browser'),
// http: require.resolve('stream-http'),
// https: require.resolve('https-browserify'),
// assert: require.resolve('assert/'),
// events: require.resolve('events/'),
// 'ansi-html-community': require.resolve('ansi-html-community'),
// 'html-entities': require.resolve('html-entities'),
// constants: false,
// fs: false,
// },
}, },
module: { module: {
rules: [...rules, ...rendererRules], rules: [...rules, ...rendererRules],
@@ -89,10 +73,41 @@ module.exports = [
}, },
], ],
}), }),
// Generate an HTML file for each entry
...Object.keys(entry).map(
(name) =>
new HtmlWebpackPlugin({
template: './test/test.ejs',
filename: `${name}.html`,
chunks: [name],
inject: true,
testName: name,
})
),
// Add an index page listing all test pages
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: './test/test.ejs', templateContent: () => `
filename: `index.html`, <!DOCTYPE html>
inject: true, <html>
<head>
<meta charset="UTF-8">
<title>tlsn-js test index</title>
</head>
<body>
<h1>tlsn-js test index</h1>
<ul>
${Object.keys(entry)
.map(
(name) =>
`<li><a href="${name}.html">${name}</a></li>`
)
.join('\n')}
</ul>
</body>
</html>
`,
filename: 'index.html',
inject: false,
}), }),
], ],
stats: 'minimal', stats: 'minimal',