Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ All notable changes to `@qavajs/tx` will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]
## [0.0.14]

### Added
- XPath locator support — `page.locator()`, `locator.locator()`, and `frameLocator.locator()` now accept XPath expressions in addition to CSS selectors. Prefix with `//` (e.g. `//button[@id='submit']`) or with `xpath=` (e.g. `xpath=//button[@id='submit']`). Evaluated via `document.evaluate()` using `ORDERED_NODE_SNAPSHOT_TYPE`.
Expand All @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Command log entries no longer have a coloured left border — status is conveyed by the icon only
- Inline test log now collapses automatically when a test finishes, regardless of pass or fail
- `HtmlReporter` — each test row now displays only the leaf test name; the suite prefix is stripped since it is already shown in the group header
- **Front-end / runner decoupling** — control panel HTML, CSS, and browser scripts are now standalone files edited with full IDE support; the test runner import (`executeTests`) is isolated behind `src/panel/runner-bridge.ts`; HTML-generation functions live in `src/panel/render.ts` with no DOM or runner dependencies; shared element IDs are declared once in `src/panel/selectors.ts` and used by both the HTML template and `devPanel.ts`; the `tsLoader` now registers `.css`, `.html`, and `.iife.js` require hooks so reporters load correctly from source during `--test` runs without a prior build step

### Removed
- `:has-text("…")` pseudo-class support in selectors — use `locator.filter({ hasText: '…' })` instead
Expand Down
6 changes: 5 additions & 1 deletion build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ if (!watch) {

const sharedOpts = watch ? { watch: true } : {};

const textLoader = { loader: { '.html': 'text', '.css': 'text', '.iife.js': 'text' } };

await esbuild.build({
...sharedOpts,
...textLoader,
entryPoints: ['src/index.ts'],
bundle: true,
platform: 'node',
Expand All @@ -33,10 +36,11 @@ await esbuild.build({

await esbuild.build({
...sharedOpts,
...textLoader,
entryPoints: [
'src/reporters/ConsoleReporter.ts',
'src/reporters/HtmlReporter.ts',
'src/reporters/JUnitReporter.ts',
'src/reporters/JunitReporter.ts',
],
bundle: true,
platform: 'node',
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@qavajs/tx",
"version": "0.0.13",
"version": "0.0.14",
"description": "@qavajs/tx — testing framework via Hammerhead proxy",
"license": "MIT",
"author": "Oleksandr Halichenko",
Expand Down
3 changes: 3 additions & 0 deletions src/assets.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module '*.css' { const content: string; export default content; }
declare module '*.html' { const content: string; export default content; }
declare module '*.iife.js' { const content: string; export default content; }
75 changes: 37 additions & 38 deletions src/browser/devPanel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { fromProxiedUrl, iframeDoc, wsOnMessage, page } from './browser';
import { escHtml } from '../utils/htmlUtils';
import { SEL } from '../panel/selectors';

// ── Network panel ─────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -54,14 +55,14 @@ function _renderNetworkRow(entry: NetworkEntry): string {
}

function _updateNetworkCount() {
const el = document.getElementById('networkCount');
const el = document.getElementById(SEL.networkCount);
if (el) el.textContent = _networkEntries.length > 0
? _networkEntries.length + ' request' + (_networkEntries.length !== 1 ? 's' : '')
: '';
}

function _appendNetworkEntry(entry: NetworkEntry) {
const list = document.getElementById('networkList');
const list = document.getElementById(SEL.networkList);
if (!list) return;
const empty = list.querySelector('.tx-empty-network');
if (empty) empty.remove();
Expand All @@ -74,7 +75,7 @@ function _appendNetworkEntry(entry: NetworkEntry) {
}

function _refreshNetworkRow(entry: NetworkEntry) {
const list = document.getElementById('networkList');
const list = document.getElementById(SEL.networkList);
const row = list?.querySelector<HTMLElement>('[data-net-id="' + entry.id + '"]');
if (!row) return;
const tmp = document.createElement('div');
Expand All @@ -84,7 +85,7 @@ function _refreshNetworkRow(entry: NetworkEntry) {
row.replaceWith(newRow);
_updateNetworkCount();
if (_selectedNetworkId === entry.id) {
const detailBody = document.getElementById('networkDetailBody');
const detailBody = document.getElementById(SEL.networkDetailBody);
if (detailBody) detailBody.innerHTML = _renderNetworkDetail(entry);
}
}
Expand Down Expand Up @@ -160,17 +161,17 @@ function _openNetworkDetail(id: number) {
document.querySelectorAll<HTMLElement>('.tx-network-row.selected').forEach(el => el.classList.remove('selected'));
document.querySelector<HTMLElement>('[data-net-id="' + id + '"]')?.classList.add('selected');
_selectedNetworkId = id;
const detail = document.getElementById('networkDetail');
const detailTitle = document.getElementById('networkDetailTitle');
const detailBody = document.getElementById('networkDetailBody');
const detail = document.getElementById(SEL.networkDetail);
const detailTitle = document.getElementById(SEL.networkDetailTitle);
const detailBody = document.getElementById(SEL.networkDetailBody);
if (!detail || !detailBody) return;
detail.classList.add('open');
if (detailTitle) detailTitle.textContent = entry.method + ' ' + _netShortUrl(entry.url);
detailBody.innerHTML = _renderNetworkDetail(entry);
}

(window as any).closeNetworkDetail = () => {
document.getElementById('networkDetail')?.classList.remove('open');
document.getElementById(SEL.networkDetail)?.classList.remove('open');
document.querySelectorAll<HTMLElement>('.tx-network-row.selected').forEach(el => el.classList.remove('selected'));
_selectedNetworkId = null;
};
Expand All @@ -180,8 +181,8 @@ function _openNetworkDetail(id: number) {
_networkCounter = 0;
_hhReqMap.clear();
_selectedNetworkId = null;
document.getElementById('networkDetail')?.classList.remove('open');
const list = document.getElementById('networkList');
document.getElementById(SEL.networkDetail)?.classList.remove('open');
const list = document.getElementById(SEL.networkList);
if (list) list.innerHTML = '<div class="tx-empty-network">No requests yet</div>';
_updateNetworkCount();
};
Expand All @@ -202,9 +203,9 @@ let _consoleErrorCount = 0;
const _MAX_CONSOLE = 1000;

function _updateConsoleBadge() {
const count = document.getElementById('consoleCount');
const badge = document.getElementById('consoleErrorBadge');
const panel = document.getElementById('networkPanel');
const count = document.getElementById(SEL.consoleCount);
const badge = document.getElementById(SEL.consoleErrorBadge);
const panel = document.getElementById(SEL.networkPanel);
const isConsoleTab = panel?.dataset.activeTab === 'console';
if (count) {
count.textContent = _consoleEntries.length > 0 ? String(_consoleEntries.length) : '';
Expand All @@ -217,7 +218,7 @@ function _updateConsoleBadge() {
}

function _appendConsoleEntry(entry: ConsoleEntry) {
const list = document.getElementById('consoleList');
const list = document.getElementById(SEL.consoleList);
if (!list) return;
const empty = list.querySelector('.tx-empty-network');
if (empty) empty.remove();
Expand All @@ -239,13 +240,12 @@ function _appendConsoleEntry(entry: ConsoleEntry) {
let _activeDevTab: 'network' | 'console' | 'selector' = 'network';

function _openDevPanel(tab: 'network' | 'console' | 'selector') {
const panel = document.getElementById('networkPanel');
const panel = document.getElementById(SEL.networkPanel);
if (!panel) return;
const alreadyOpen = panel.classList.contains('open');
if (alreadyOpen && _activeDevTab === tab) {
panel.classList.remove('open');
document.getElementById('networkToggleBtn')?.classList.remove('active');
document.getElementById('consoleToggleBtn')?.classList.remove('active');
document.getElementById(SEL.networkToggleBtn)?.classList.remove('active');
_clearSelectorHighlights();
return;
}
Expand All @@ -258,31 +258,31 @@ function _openDevPanel(tab: 'network' | 'console' | 'selector') {
}

function _switchDevTabInternal(tab: 'network' | 'console' | 'selector') {
const panel = document.getElementById('networkPanel');
const panel = document.getElementById(SEL.networkPanel);
if (!panel) return;
if (_activeDevTab === 'selector' && tab !== 'selector') _clearSelectorHighlights();
_activeDevTab = tab;
panel.dataset.activeTab = tab;
document.getElementById('devTabNetwork')?.classList.toggle('active', tab === 'network');
document.getElementById('devTabConsole')?.classList.toggle('active', tab === 'console');
document.getElementById('devTabSelector')?.classList.toggle('active', tab === 'selector');
document.getElementById('devTabContentNetwork')?.classList.toggle('active', tab === 'network');
document.getElementById('devTabContentConsole')?.classList.toggle('active', tab === 'console');
document.getElementById('devTabContentSelector')?.classList.toggle('active', tab === 'selector');
document.getElementById('networkToggleBtn')?.classList.toggle('active', panel.classList.contains('open'));
document.getElementById(SEL.devTabNetwork)?.classList.toggle('active', tab === 'network');
document.getElementById(SEL.devTabConsole)?.classList.toggle('active', tab === 'console');
document.getElementById(SEL.devTabSelector)?.classList.toggle('active', tab === 'selector');
document.getElementById(SEL.devTabContentNetwork)?.classList.toggle('active', tab === 'network');
document.getElementById(SEL.devTabContentConsole)?.classList.toggle('active', tab === 'console');
document.getElementById(SEL.devTabContentSelector)?.classList.toggle('active', tab === 'selector');
document.getElementById(SEL.networkToggleBtn)?.classList.toggle('active', panel.classList.contains('open'));
if (tab === 'console') {
_consoleErrorCount = 0;
_updateConsoleBadge();
}
if (tab === 'selector') {
const input = document.getElementById('selectorInput') as HTMLInputElement | null;
const input = document.getElementById(SEL.selectorInput) as HTMLInputElement | null;
if (input?.value) _runSelectorQuery(input.value);
setTimeout(() => input?.focus(), 50);
}
}

(window as any).switchDevTab = (tab: 'network' | 'console' | 'selector') => {
const panel = document.getElementById('networkPanel');
const panel = document.getElementById(SEL.networkPanel);
if (!panel) return;
if (!panel.classList.contains('open')) {
panel.classList.add('open');
Expand All @@ -293,11 +293,10 @@ function _switchDevTabInternal(tab: 'network' | 'console' | 'selector') {
};

(window as any).toggleNetworkPanel = () => {
const panel = document.getElementById('networkPanel');
const panel = document.getElementById(SEL.networkPanel);
if (panel?.classList.contains('open')) {
panel.classList.remove('open');
document.getElementById('networkToggleBtn')?.classList.remove('active');
document.getElementById('consoleToggleBtn')?.classList.remove('active');
document.getElementById(SEL.networkToggleBtn)?.classList.remove('active');
_clearSelectorHighlights();
} else {
_openDevPanel(_activeDevTab);
Expand All @@ -315,7 +314,7 @@ function _switchDevTabInternal(tab: 'network' | 'console' | 'selector') {
_consoleEntries.length = 0;
_consoleCounter = 0;
_consoleErrorCount = 0;
const list = document.getElementById('consoleList');
const list = document.getElementById(SEL.consoleList);
if (list) list.innerHTML = '<div class="tx-empty-network">No console output yet</div>';
_updateConsoleBadge();
}
Expand Down Expand Up @@ -353,9 +352,9 @@ function _describeElement(el: Element, idx: number): string {
}

function _runSelectorQuery(selector: string) {
const input = document.getElementById('selectorInput') as HTMLInputElement | null;
const status = document.getElementById('selectorStatus');
const matchList = document.getElementById('selectorMatches');
const input = document.getElementById(SEL.selectorInput) as HTMLInputElement | null;
const status = document.getElementById(SEL.selectorStatus);
const matchList = document.getElementById(SEL.selectorMatches);
if (!status || !matchList) return;

_clearSelectorHighlights();
Expand Down Expand Up @@ -415,16 +414,16 @@ function _runSelectorQuery(selector: string) {
(window as any).runSelectorQuery = _runSelectorQuery;

(window as any).clearSelectorQuery = () => {
const input = document.getElementById('selectorInput') as HTMLInputElement | null;
const input = document.getElementById(SEL.selectorInput) as HTMLInputElement | null;
if (input) { input.value = ''; input.className = 'tx-selector-input'; }
_runSelectorQuery('');
};

// ── Network panel resizer ─────────────────────────────────────────────────────

export function initNetworkResizer(): void {
const panel = document.getElementById('networkPanel');
const handle = document.getElementById('networkResizeHandle');
const panel = document.getElementById(SEL.networkPanel);
const handle = document.getElementById(SEL.networkResizeHandle);
if (!panel || !handle) return;

handle.addEventListener('mousedown', (e: MouseEvent) => {
Expand Down Expand Up @@ -490,7 +489,7 @@ export function initNetworkListeners(): void {
_refreshNetworkRow(entry);
});

document.getElementById('networkList')?.addEventListener('click', (e: MouseEvent) => {
document.getElementById(SEL.networkList)?.addEventListener('click', (e: MouseEvent) => {
const row = (e.target as Element).closest<HTMLElement>('.tx-network-row');
if (!row) return;
const id = Number(row.getAttribute('data-net-id'));
Expand Down
Loading
Loading