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",
|
"postinstall": "patch-package",
|
||||||
"test": "npm run test:integration && npm run test:unit",
|
"test": "npm run test:integration && npm run test:unit",
|
||||||
"test:integration": "playwright test",
|
"test:integration": "playwright test",
|
||||||
|
"test:screenshots": "playwright test -c playwright.screenshot.config.ts",
|
||||||
"test:unit": "vitest"
|
"test:unit": "vitest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -10,7 +10,7 @@ const config: PlaywrightTestConfig = {
|
||||||
port: 4173
|
port: 4173
|
||||||
},
|
},
|
||||||
reporter: process.env.CI ? 'github' : 'list',
|
reporter: process.env.CI ? 'github' : 'list',
|
||||||
testDir: 'tests',
|
testDir: 'tests/playwright',
|
||||||
testMatch: /(.+\.)?(test|spec)\.[jt]s/
|
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 {
|
.btn-group-sm .btn {
|
||||||
font-size: 0.8rem;
|
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 {
|
#customText {
|
||||||
|
@ -63,7 +80,7 @@ nav {
|
||||||
.btclock {
|
.btclock {
|
||||||
background: #000;
|
background: #000;
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: calc(3vw + 3vh);
|
font-size: calc(2vw + 2vh);
|
||||||
font-family: 'Antonio', sans-serif;
|
font-family: 'Antonio', sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
@ -104,7 +121,7 @@ nav {
|
||||||
flex-direction: column; /* Stack the text and line vertically */
|
flex-direction: column; /* Stack the text and line vertically */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around; /* Distribute items with space between */
|
justify-content: space-around; /* Distribute items with space between */
|
||||||
padding: 10px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitText div:first-child::after {
|
.splitText div:first-child::after {
|
||||||
|
@ -116,7 +133,7 @@ nav {
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitText {
|
.splitText {
|
||||||
font-size: calc(0.5vw + 1vh);
|
font-size: calc(0.3vw + 1vh);
|
||||||
|
|
||||||
.top-text,
|
.top-text,
|
||||||
.bottom-text {
|
.bottom-text {
|
||||||
|
|
|
@ -200,7 +200,7 @@
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
{/if}
|
{/if}
|
||||||
<section class="row row-cols-lg-auto align-items-end">
|
<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>
|
<label for="firmwareFile" class="form-label">Firmware file ({getFirmwareBinaryName()})</label>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
|
@ -216,7 +216,7 @@
|
||||||
>Update firmware</Button
|
>Update firmware</Button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="col mt-2">
|
<div class="col flex-fill">
|
||||||
<label for="webuiFile" class="form-label">WebUI file (littlefs.bin)</label>
|
<label for="webuiFile" class="form-label">WebUI file (littlefs.bin)</label>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
|
|
|
@ -1,114 +1,7 @@
|
||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
import { initMock, settingsJson, statusJson } from '../shared';
|
||||||
|
|
||||||
const statusJson = {
|
test.beforeEach(initMock);
|
||||||
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('index page has expected columns control, status, settings', async ({ page }) => {
|
test('index page has expected columns control, status, settings', async ({ page }) => {
|
||||||
await page.goto('/');
|
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 }) => {
|
test('info message when fetch eur price is enabled', async ({ page }) => {
|
||||||
|
delete (settingsJson as { actCurrencies?: string[] }).actCurrencies;
|
||||||
|
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await page.getByRole('button', { name: 'Show all' }).click();
|
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