forked from btclock/webui
Fix screen selector UI, add screenshot maker
This commit is contained in:
parent
25e91b2086
commit
68c247f3cc
8 changed files with 281 additions and 115 deletions
|
@ -13,6 +13,7 @@
|
|||
"postinstall": "patch-package",
|
||||
"test": "npm run test:integration && npm run test:unit",
|
||||
"test:integration": "playwright test",
|
||||
"test:screenshots": "playwright test -c playwright.screenshot.config.ts",
|
||||
"test:unit": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -10,7 +10,7 @@ const config: PlaywrightTestConfig = {
|
|||
port: 4173
|
||||
},
|
||||
reporter: process.env.CI ? 'github' : 'list',
|
||||
testDir: 'tests',
|
||||
testDir: 'tests/playwright',
|
||||
testMatch: /(.+\.)?(test|spec)\.[jt]s/
|
||||
};
|
||||
|
||||
|
|
36
playwright.screenshot.config.ts
Normal file
36
playwright.screenshot.config.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
use: {
|
||||
locale: 'en-GB',
|
||||
timezoneId: 'Europe/Amsterdam'
|
||||
},
|
||||
webServer: {
|
||||
command: 'npm run build && npm run preview',
|
||||
port: 4173
|
||||
},
|
||||
testDir: './tests/screenshots',
|
||||
outputDir: './test-results/screenshots',
|
||||
projects: [
|
||||
{
|
||||
name: 'MacBook Air 13 inch',
|
||||
use: {
|
||||
viewport: { width: 1440, height: 900 }
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'iPhone 14 Pro',
|
||||
use: { ...devices['iPhone 14 Pro'] }
|
||||
},
|
||||
{
|
||||
name: 'iPhone 15 Pro Landscape',
|
||||
use: { ...devices['iPhone 15 Pro Landscape'] }
|
||||
},
|
||||
{
|
||||
name: 'MacBook Pro 14 inch',
|
||||
use: {
|
||||
viewport: { width: 1512, height: 982 }
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
|
@ -53,6 +53,23 @@ nav {
|
|||
|
||||
.btn-group-sm .btn {
|
||||
font-size: 0.8rem;
|
||||
// text-overflow: ellipsis;
|
||||
// white-space: nowrap;
|
||||
// overflow: hidden;
|
||||
// width: 4rem;
|
||||
}
|
||||
|
||||
.btn-group-sm {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
gap: 0.25rem !important;
|
||||
}
|
||||
|
||||
/* Remove the border radius override that Bootstrap applies */
|
||||
.btn-group-sm > .btn {
|
||||
border-radius: 0.25rem !important;
|
||||
margin: 0 !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
#customText {
|
||||
|
@ -63,7 +80,7 @@ nav {
|
|||
.btclock {
|
||||
background: #000;
|
||||
display: flex;
|
||||
font-size: calc(3vw + 3vh);
|
||||
font-size: calc(2vw + 2vh);
|
||||
font-family: 'Antonio', sans-serif;
|
||||
font-weight: 400;
|
||||
padding: 10px;
|
||||
|
@ -104,7 +121,7 @@ nav {
|
|||
flex-direction: column; /* Stack the text and line vertically */
|
||||
align-items: center;
|
||||
justify-content: space-around; /* Distribute items with space between */
|
||||
padding: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.splitText div:first-child::after {
|
||||
|
@ -116,7 +133,7 @@ nav {
|
|||
}
|
||||
|
||||
.splitText {
|
||||
font-size: calc(0.5vw + 1vh);
|
||||
font-size: calc(0.3vw + 1vh);
|
||||
|
||||
.top-text,
|
||||
.bottom-text {
|
||||
|
|
|
@ -200,7 +200,7 @@
|
|||
<p>Loading...</p>
|
||||
{/if}
|
||||
<section class="row row-cols-lg-auto align-items-end">
|
||||
<div class="col-12">
|
||||
<div class="col flex-fill">
|
||||
<label for="firmwareFile" class="form-label">Firmware file ({getFirmwareBinaryName()})</label>
|
||||
<input
|
||||
type="file"
|
||||
|
@ -216,7 +216,7 @@
|
|||
>Update firmware</Button
|
||||
>
|
||||
</div>
|
||||
<div class="col mt-2">
|
||||
<div class="col flex-fill">
|
||||
<label for="webuiFile" class="form-label">WebUI file (littlefs.bin)</label>
|
||||
<input
|
||||
type="file"
|
||||
|
|
|
@ -1,114 +1,7 @@
|
|||
import { expect, test } from '@playwright/test';
|
||||
import { initMock, settingsJson, statusJson } from '../shared';
|
||||
|
||||
const statusJson = {
|
||||
currentScreen: 0,
|
||||
numScreens: 7,
|
||||
timerRunning: true,
|
||||
espUptime: 4479,
|
||||
espFreeHeap: 58508,
|
||||
espHeapSize: 342108,
|
||||
connectionStatus: { price: true, blocks: true },
|
||||
rssi: -66,
|
||||
data: ['BLOCK/HEIGHT', '8', '1', '8', '0', '2', '6'],
|
||||
rendered: ['BLOCK/HEIGHT', '8', '1', '8', '0', '2', '6'],
|
||||
leds: [
|
||||
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
||||
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
||||
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
||||
{ red: 0, green: 0, blue: 0, hex: '#000000' }
|
||||
]
|
||||
};
|
||||
|
||||
const settingsJson = {
|
||||
numScreens: 7,
|
||||
fgColor: 415029,
|
||||
bgColor: 0,
|
||||
timerSeconds: 1800,
|
||||
timerRunning: true,
|
||||
minSecPriceUpd: 30,
|
||||
fullRefreshMin: 60,
|
||||
wpTimeout: 600,
|
||||
tzOffset: 0,
|
||||
useBitcoinNode: false,
|
||||
mempoolInstance: 'mempool.space',
|
||||
ledTestOnPower: true,
|
||||
ledFlashOnUpd: true,
|
||||
ledBrightness: 128,
|
||||
stealFocus: true,
|
||||
mcapBigChar: true,
|
||||
mdnsEnabled: true,
|
||||
otaEnabled: true,
|
||||
fetchEurPrice: false,
|
||||
hostnamePrefix: 'btclock',
|
||||
hostname: 'btclock-d60b14',
|
||||
ip: '192.168.20.231',
|
||||
txPower: 78,
|
||||
gitRev: '25d8b92bcbc8938417c140355ea3ba99ff9eb4b7',
|
||||
gitTag: '3.1.9',
|
||||
bitaxeEnabled: false,
|
||||
bitaxeHostname: 'bitaxe1',
|
||||
nostrZapNotify: true,
|
||||
hwRev: 'REV_A_EPD_2_13',
|
||||
fsRev: '4c5d9616212b27e3f05c35370f0befcf2c5a04b2',
|
||||
nostrZapPubkey: 'b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422',
|
||||
lastBuildTime: '1700666677',
|
||||
screens: [
|
||||
{ id: 0, name: 'Block Height', enabled: true },
|
||||
{ id: 1, name: 'Sats per dollar', enabled: true },
|
||||
{ id: 2, name: 'Ticker', enabled: true },
|
||||
{ id: 3, name: 'Time', enabled: true },
|
||||
{ id: 4, name: 'Halving countdown', enabled: true },
|
||||
{ id: 5, name: 'Market Cap', enabled: true }
|
||||
]
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.route('*/**/api/status', async (route) => {
|
||||
await route.fulfill({ json: statusJson });
|
||||
});
|
||||
|
||||
await page.route('*/**/api/show/screen/1', async (route) => {
|
||||
//if (route.request().url().includes('*/**/api/show/screen/1')) {
|
||||
statusJson.currentScreen = 1;
|
||||
statusJson.data = ['MSCW/TIME', ' ', ' ', '2', '6', '4', '4'];
|
||||
statusJson.rendered = statusJson.data;
|
||||
//}
|
||||
|
||||
await route.fulfill({ json: statusJson });
|
||||
});
|
||||
|
||||
await page.route('*/**/api/show/screen/2', async (route) => {
|
||||
statusJson.currentScreen = 2;
|
||||
statusJson.data = ['BTC/USD', '$', '3', '7', '8', '2', '4'];
|
||||
statusJson.rendered = statusJson.data;
|
||||
|
||||
await route.fulfill({ json: statusJson });
|
||||
});
|
||||
|
||||
await page.route('*/**/api/show/screen/4', async (route) => {
|
||||
statusJson.currentScreen = 4;
|
||||
statusJson.data = ['BIT/COIN', 'HALV/ING', '0/YRS', '149/DAYS', '8/HRS', '30/MINS', 'TO/GO'];
|
||||
statusJson.rendered = statusJson.data;
|
||||
|
||||
await route.fulfill({ json: statusJson });
|
||||
});
|
||||
|
||||
await page.route('*/**/api/settings', async (route) => {
|
||||
await route.fulfill({ json: settingsJson });
|
||||
});
|
||||
|
||||
await page.route('**/events', (route) => {
|
||||
const newStatus = statusJson;
|
||||
newStatus.data = ['BLOCK/HEIGHT', '8', '0', '0', '8', '1', '5'];
|
||||
|
||||
// Respond with a custom SSE message
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'text/event-stream',
|
||||
json: `${JSON.stringify(newStatus)}\n\n`
|
||||
});
|
||||
});
|
||||
});
|
||||
test.beforeEach(initMock);
|
||||
|
||||
test('index page has expected columns control, status, settings', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
@ -181,6 +74,8 @@ test('time values can not be zero or negative', async ({ page }) => {
|
|||
});
|
||||
|
||||
test('info message when fetch eur price is enabled', async ({ page }) => {
|
||||
delete (settingsJson as { actCurrencies?: string[] }).actCurrencies;
|
||||
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: 'Show all' }).click();
|
||||
|
77
tests/screenshots/viewport-screenshots.spec.ts
Normal file
77
tests/screenshots/viewport-screenshots.spec.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
import { initMock, settingsJson } from '../shared';
|
||||
|
||||
test.beforeEach(initMock);
|
||||
|
||||
test('capture screenshots across devices', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.getByRole('heading', { name: 'Control' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Status' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Settings' })).toBeVisible();
|
||||
|
||||
await page.screenshot({
|
||||
path: `./test-results/screenshots/default-${test.info().project.name}.png`,
|
||||
fullPage: true
|
||||
});
|
||||
});
|
||||
|
||||
test('capture screenshots across devices with bitaxe screens', async ({ page }) => {
|
||||
settingsJson.screens = [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Block Height',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Time',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Halving countdown',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Block Fee Rate',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'Sats per dollar',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: 'Ticker',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 30,
|
||||
name: 'Market Cap',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 80,
|
||||
name: 'BitAxe Hashrate',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 81,
|
||||
name: 'BitAxe Best Difficulty',
|
||||
enabled: true
|
||||
}
|
||||
];
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.getByRole('heading', { name: 'Control' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Status' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Settings' })).toBeVisible();
|
||||
|
||||
await page.screenshot({
|
||||
path: `./test-results/screenshots/bitaxe-${test.info().project.name}.png`,
|
||||
fullPage: true
|
||||
});
|
||||
});
|
140
tests/shared.ts
Normal file
140
tests/shared.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
export const statusJson = {
|
||||
currentScreen: 0,
|
||||
numScreens: 7,
|
||||
timerRunning: true,
|
||||
espUptime: 4479,
|
||||
espFreeHeap: 58508,
|
||||
espHeapSize: 342108,
|
||||
connectionStatus: { price: true, blocks: true },
|
||||
rssi: -66,
|
||||
data: ['BLOCK/HEIGHT', '8', '1', '8', '0', '2', '6'],
|
||||
rendered: ['BLOCK/HEIGHT', '8', '1', '8', '0', '2', '6'],
|
||||
leds: [
|
||||
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
||||
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
||||
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
||||
{ red: 0, green: 0, blue: 0, hex: '#000000' }
|
||||
]
|
||||
};
|
||||
|
||||
export const settingsJson = {
|
||||
numScreens: 7,
|
||||
fgColor: 415029,
|
||||
bgColor: 0,
|
||||
timerSeconds: 1800,
|
||||
timerRunning: true,
|
||||
minSecPriceUpd: 30,
|
||||
fullRefreshMin: 60,
|
||||
wpTimeout: 600,
|
||||
tzOffset: 0,
|
||||
useBitcoinNode: false,
|
||||
mempoolInstance: 'mempool.space',
|
||||
ledTestOnPower: true,
|
||||
ledFlashOnUpd: true,
|
||||
ledBrightness: 128,
|
||||
stealFocus: true,
|
||||
mcapBigChar: true,
|
||||
mdnsEnabled: true,
|
||||
otaEnabled: true,
|
||||
fetchEurPrice: false,
|
||||
hostnamePrefix: 'btclock',
|
||||
hostname: 'btclock-d60b14',
|
||||
ip: '192.168.20.231',
|
||||
txPower: 78,
|
||||
gitRev: '25d8b92bcbc8938417c140355ea3ba99ff9eb4b7',
|
||||
gitTag: '3.1.9',
|
||||
bitaxeEnabled: false,
|
||||
bitaxeHostname: 'bitaxe1',
|
||||
nostrZapNotify: true,
|
||||
hwRev: 'REV_A_EPD_2_13',
|
||||
fsRev: '4c5d9616212b27e3f05c35370f0befcf2c5a04b2',
|
||||
nostrZapPubkey: 'b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422',
|
||||
lastBuildTime: '1700666677',
|
||||
screens: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Block Height',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Time',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Halving countdown',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Block Fee Rate',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'Sats per dollar',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: 'Ticker',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 30,
|
||||
name: 'Market Cap',
|
||||
enabled: true
|
||||
}
|
||||
],
|
||||
actCurrencies: ['USD', 'EUR'],
|
||||
availableCurrencies: ['USD', 'EUR', 'GBP', 'JPY', 'AUD', 'CAD']
|
||||
};
|
||||
|
||||
export const initMock = async ({ page }) => {
|
||||
await page.route('*/**/api/status', async (route) => {
|
||||
await route.fulfill({ json: statusJson });
|
||||
});
|
||||
|
||||
await page.route('*/**/api/show/screen/1', async (route) => {
|
||||
//if (route.request().url().includes('*/**/api/show/screen/1')) {
|
||||
statusJson.currentScreen = 1;
|
||||
statusJson.data = ['MSCW/TIME', ' ', ' ', '2', '6', '4', '4'];
|
||||
statusJson.rendered = statusJson.data;
|
||||
//}
|
||||
|
||||
await route.fulfill({ json: statusJson });
|
||||
});
|
||||
|
||||
await page.route('*/**/api/show/screen/2', async (route) => {
|
||||
statusJson.currentScreen = 2;
|
||||
statusJson.data = ['BTC/USD', '$', '3', '7', '8', '2', '4'];
|
||||
statusJson.rendered = statusJson.data;
|
||||
|
||||
await route.fulfill({ json: statusJson });
|
||||
});
|
||||
|
||||
await page.route('*/**/api/show/screen/4', async (route) => {
|
||||
statusJson.currentScreen = 4;
|
||||
statusJson.data = ['BIT/COIN', 'HALV/ING', '0/YRS', '149/DAYS', '8/HRS', '30/MINS', 'TO/GO'];
|
||||
statusJson.rendered = statusJson.data;
|
||||
|
||||
await route.fulfill({ json: statusJson });
|
||||
});
|
||||
|
||||
await page.route('*/**/api/settings', async (route) => {
|
||||
await route.fulfill({ json: settingsJson });
|
||||
});
|
||||
|
||||
await page.route('**/events', (route) => {
|
||||
const newStatus = statusJson;
|
||||
newStatus.data = ['BLOCK/HEIGHT', '8', '0', '0', '8', '1', '5'];
|
||||
|
||||
// Respond with a custom SSE message
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'text/event-stream',
|
||||
json: `${JSON.stringify(newStatus)}\n\n`
|
||||
});
|
||||
});
|
||||
};
|
Loading…
Reference in a new issue