Skip to content

Commit

Permalink
feat(explorer): cluster testing
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfreska committed Feb 21, 2025
1 parent 60a0d1f commit 857d889
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ deploy.log

# Next.js
.next
.next-*

# builds
dist
Expand Down
11 changes: 5 additions & 6 deletions apps/explorer-e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { defineConfig, devices } from '@playwright/test'
import { nxE2EPreset } from '@nx/playwright/preset'

import { workspaceRoot } from '@nx/devkit'

// For CI, you may want to set BASE_URL to the deployed application.
const baseURL = process.env['BASE_URL'] || 'http://localhost:3005'
// // For CI, you may want to set BASE_URL to the deployed application.
// const baseURL = process.env['BASE_URL'] || 'http://localhost:3005'

/**
* Read environment variables from file.
Expand All @@ -20,13 +19,13 @@ export default defineConfig({
reporter: process.env.CI ? 'blob' : 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
baseURL,
// baseURL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
video: 'on-first-retry',
},
// Timeout per test.
timeout: 60_000,
timeout: 180_000,
expect: {
// Raise the timeout because it is running against next dev mode
// which requires compilation the first to a page is visited.
Expand All @@ -36,7 +35,7 @@ export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: {
command: 'npx nx run explorer:serve:development-testnet-zen',
url: baseURL,
// url: baseURL,
reuseExistingServer: !process.env.CI,
cwd: workspaceRoot,
},
Expand Down
13 changes: 11 additions & 2 deletions apps/explorer-e2e/src/fixtures/ExplorerApp.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Locator, Page } from 'playwright'
import { SEARCHBAR } from './constants'
import path from 'path'

export class ExplorerApp {
private readonly searchBar: Locator
public readonly baseUrl: string

constructor(public readonly page: Page) {
constructor(public readonly page: Page, baseUrl?: string) {
this.searchBar = this.page.locator(SEARCHBAR)
this.baseUrl = baseUrl
}

async navigateBySearchBar(searchTerm: string) {
Expand All @@ -15,6 +18,12 @@ export class ExplorerApp {
}

async goTo(url: string) {
await this.page.goto(url)
if (this.baseUrl) {
await this.page.goto(path.join(this.baseUrl, url))
} else {
// If no baseUrl is provided, use the default address and port for:
// explorer:serve:development-testnet-zen.
await this.page.goto('http://localhost:3005')
}
}
}
67 changes: 67 additions & 0 deletions apps/explorer-e2e/src/fixtures/cluster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Explored } from '@siafoundation/explored-js'
import { Hostd } from '@siafoundation/hostd-js'
import { Bus } from '@siafoundation/renterd-js'
import { startWebServerCluster, stopWebServer } from './webServerCluster'
import {
clusterd,
renterdWaitForContracts,
setupCluster,
teardownCluster,
} from '@siafoundation/clusterd'

export async function startCluster() {
const exploredCount = 1
const renterdCount = 1
const hostdCount = 3
await setupCluster({
exploredCount,
renterdCount,
hostdCount,
})
const renterd = clusterd.nodes.find((n) => n.type === 'renterd')
const explored = clusterd.nodes.find((n) => n.type === 'explored')
const hostds = clusterd.nodes.filter((n) => n.type === 'hostd')
await renterdWaitForContracts({ renterdNode: renterd, hostdCount })
const daemons = {
renterd,
explored,
hostds,
}
const apis = {
renterd: Bus({
api: `${renterd.apiAddress}/api`,
password: renterd.password,
}),
explored: Explored({
api: `${explored.apiAddress}/api`,
password: explored.password,
}),
hostds: hostds.map((h) =>
Hostd({
api: `${h.apiAddress}/api`,
password: h.password,
})
),
}
const { baseUrl } = await startWebServerCluster({
exploredAddress: daemons.explored.apiAddress,
})
console.log(`
webServerUrl: ${baseUrl}
clusterd: http://localhost:${clusterd.managementPort}
explored: ${daemons.explored.apiAddress}
renterd: ${daemons.renterd.apiAddress}
hostds: ${daemons.hostds.map((h) => h.apiAddress)}
`)
return {
webServerUrl: baseUrl,
clusterd,
apis,
daemons,
}
}

export async function stopCluster() {
stopWebServer()
teardownCluster()
}
77 changes: 77 additions & 0 deletions apps/explorer-e2e/src/fixtures/webServerCluster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ChildProcess, spawn } from 'child_process'
import { workspaceRoot } from '@nx/devkit'
import net from 'net'

let server: ChildProcess
let baseUrl: string

// Starts the explorer app webserver configured to run against the testnet
// cluster provided via the NEXT_PUBLIC_EXPLORED_ADDRESS environment variable.
export async function startWebServerCluster({
exploredAddress,
}: {
exploredAddress: string
}) {
const port = await findFreePort()
server = spawn(
'npx',
[
'nx',
'run',
'explorer:serve:development-testnet-cluster',
'--port',
port.toString(),
],
{
cwd: workspaceRoot,
shell: true,
env: {
...process.env,
NEXT_PUBLIC_EXPLORED_ADDRESS: exploredAddress,
},
}
)

server.stdout.on('data', (data) => {
console.log(data.toString())
})

server.stderr.on('data', (data) => {
console.error(data.toString())
})

// Wait until stdout prints "Ready", eg:
// ✓ Starting...
// ✓ Ready in 1606ms
await new Promise((resolve) => {
server.stdout.on('data', (data) => {
if (data.toString().includes('Ready')) {
console.log('Server ready')
resolve(true)
}
})
})
baseUrl = `http://localhost:${port}`
return {
baseUrl,
}
}

export function stopWebServer() {
console.log('Stopping webserver: ', baseUrl)
server.kill() // Kill the server after each test
}

async function findFreePort(): Promise<number> {
return new Promise((res) => {
const srv = net.createServer()
srv.listen(0, () => {
const addr = srv.address()
if (typeof addr === 'string') {
throw new Error('Address is a string')
}
const port = addr.port
srv.close(() => res(port))
})
})
}
25 changes: 18 additions & 7 deletions apps/explorer-e2e/src/specs/address.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
import { test, expect } from '@playwright/test'
import { ExplorerApp } from '../fixtures/ExplorerApp'
import { TEST_ADDRESS_1 } from '../fixtures/constants'
import { startCluster, stopCluster } from '../fixtures/cluster'

let explorerApp: ExplorerApp
let cluster: Awaited<ReturnType<typeof startCluster>>

test.beforeEach(async ({ page }) => {
explorerApp = new ExplorerApp(page)
cluster = await startCluster()
explorerApp = new ExplorerApp(page, cluster.webServerUrl)
})

test.afterEach(async () => {
await stopCluster()
})

test('address can be searched by id', async ({ page }) => {
const wallet = await cluster.apis.renterd.wallet()
await explorerApp.goTo('/')
await explorerApp.navigateBySearchBar(TEST_ADDRESS_1.id)

await expect(page.getByText(TEST_ADDRESS_1.display.title)).toBeVisible()
await explorerApp.navigateBySearchBar(wallet.data.address)
await expect(
page.getByText(`Address ${wallet.data.address.slice(0, 5)}`)
).toBeVisible()
})

test('address can be directly navigated to by id', async ({ page }) => {
await explorerApp.goTo('/address/' + TEST_ADDRESS_1.id)

await expect(page.getByText(TEST_ADDRESS_1.display.title)).toBeVisible()
const wallet = await cluster.apis.renterd.wallet()
await explorerApp.goTo('/address/' + wallet.data.address)
await expect(
page.getByText(`Address ${wallet.data.address.slice(0, 5)}`)
).toBeVisible()
})

test('address displays the intended data', async ({ page }) => {
Expand Down
13 changes: 13 additions & 0 deletions apps/explorer/config/testnet-cluster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { webLinks } from '@siafoundation/design-system'

export const network = 'zen'
export const networkName = 'Zen Testnet'
export const siteName = 'zen.siascan.com'
export const appName = 'siascan'
export const appLink = webLinks.explore.testnetZen
export const isMainnet = false

// APIs
export const faucetApi = 'https://api.siascan.com/zen/faucet'
export const siaCentralApi = 'https://api.siacentral.com/v2/zen'
export const exploredApi = `${process.env.NEXT_PUBLIC_EXPLORED_ADDRESS}/api`
14 changes: 9 additions & 5 deletions apps/explorer/next.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const { composePlugins, withNx } = require('@nx/next');
const { composePlugins, withNx } = require('@nx/next')

const runningTargetConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION

/**
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
Expand All @@ -12,10 +14,12 @@ const nextConfig = {
svgr: false,
},
output: 'standalone',
// Use the Nx target name for the dist directory
distDir: runningTargetConfiguration
? `.next-${runningTargetConfiguration}`
: '.next',
}

const plugins = [
withNx,
];
const plugins = [withNx]

module.exports = composePlugins(...plugins)(nextConfig);
module.exports = composePlugins(...plugins)(nextConfig)
14 changes: 14 additions & 0 deletions apps/explorer/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
}
]
},
"development-testnet-cluster": {
"outputPath": "dist/apps/explorer-testnet-cluster",
"fileReplacements": [
{
"replace": "apps/explorer/config/index.ts",
"with": "apps/explorer/config/testnet-cluster.ts"
}
]
},
"local": {},
"local-testnet-zen": {
"outputPath": "dist/apps/explorer-testnet-zen",
Expand Down Expand Up @@ -62,6 +71,11 @@
"dev": true,
"port": 3005
},
"development-testnet-cluster": {
"buildTarget": "explorer:build:development-testnet-cluster",
"dev": true,
"port": 3005
},
"production": {
"buildTarget": "explorer:build:production",
"dev": false
Expand Down
6 changes: 5 additions & 1 deletion apps/explorer/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@
"**/*.jsx",
"next-env.d.ts",
".next/types/**/*.ts",
"../../dist/apps/explorer/.next/types/**/*.ts"
"../../dist/apps/explorer/.next/types/**/*.ts",
"../../dist/apps/explorer-testnet-cluster/.next/types/**/*.ts",
"../../dist/apps/explorer-testnet-cluster/.next-development-testnet-cluster/types/**/*.ts",
".next-development-testnet-cluster/types/**/*.ts",
".next-development-testnet-zen/types/**/*.ts"
],
"exclude": [
"node_modules",
Expand Down
5 changes: 3 additions & 2 deletions libs/clusterd/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ export async function setupCluster({
Maybe<{ type: string; apiAddress: string; password: string }[]>
>(`http://localhost:${clusterd.managementPort}/nodes`)
const runningCount = nodes.data?.length
const totalCount = renterdCount + hostdCount + walletdCount
if (nodes.data?.length === renterdCount + hostdCount + walletdCount) {
const totalCount =
renterdCount + hostdCount + walletdCount + exploredCount
if (nodes.data?.length === totalCount) {
clusterd.nodes = nodes.data?.map((n) => {
if ('apiAddress' in n) {
return {
Expand Down
4 changes: 2 additions & 2 deletions libs/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"version": "0.5.2",
"license": "MIT",
"dependencies": {
"@playwright/test": "^1.36.0",
"playwright": "^1.42.1",
"@playwright/test": "^1.49.1",
"playwright": "^1.49.1",
"@siafoundation/react-core": "2.0.0"
},
"types": "./src/index.d.ts"
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 857d889

Please sign in to comment.