chromancer
Version:
A powerful command-line interface for automating Chrome browser using Playwright. Perfect for web scraping, automation, testing, and browser workflows.
248 lines (247 loc) • 11.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const core_1 = require("@oclif/core");
const base_js_1 = require("../base.js");
const evaluation_js_1 = require("../utils/evaluation.js");
const store_js_1 = tslib_1.__importDefault(require("./store.js"));
class Assert extends base_js_1.BaseCommand {
static description = 'Assert conditions about page elements or JavaScript expressions';
static examples = [
'<%= config.bin %> <%= command.id %> --selector "#success-message"',
'<%= config.bin %> <%= command.id %> --selector "h1" --contains "Welcome"',
'<%= config.bin %> <%= command.id %> --eval "document.title" --equals "My Page"',
'<%= config.bin %> <%= command.id %> --selector ".items" --count 5',
'<%= config.bin %> <%= command.id %> --eval "window.location.pathname" --equals "/checkout"',
'<%= config.bin %> <%= command.id %> --stored "originalPrice" --equals "99.99"',
];
static flags = {
...base_js_1.BaseCommand.baseFlags,
selector: core_1.Flags.string({
char: 's',
description: 'CSS selector to assert on',
exclusive: ['eval', 'stored'],
}),
eval: core_1.Flags.string({
char: 'e',
description: 'JavaScript expression to evaluate and assert',
exclusive: ['selector', 'stored'],
}),
stored: core_1.Flags.string({
description: 'Assert on a previously stored value',
exclusive: ['selector', 'eval'],
}),
contains: core_1.Flags.string({
description: 'Assert element text contains this value',
exclusive: ['equals', 'matches', 'greater-than', 'less-than'],
}),
equals: core_1.Flags.string({
description: 'Assert element text or evaluation result equals this value',
exclusive: ['contains', 'matches', 'greater-than', 'less-than'],
}),
matches: core_1.Flags.string({
description: 'Assert element text matches this regex pattern',
exclusive: ['contains', 'equals', 'greater-than', 'less-than'],
}),
'greater-than': core_1.Flags.string({
description: 'Assert numeric value is greater than this',
exclusive: ['contains', 'equals', 'matches', 'less-than'],
}),
'less-than': core_1.Flags.string({
description: 'Assert numeric value is less than this',
exclusive: ['contains', 'equals', 'matches', 'greater-than'],
}),
count: core_1.Flags.string({
description: 'Assert number of elements matching selector',
dependsOn: ['selector'],
exclusive: ['contains', 'equals', 'matches', 'visible', 'not-visible', 'value', 'greater-than', 'less-than'],
}),
visible: core_1.Flags.boolean({
description: 'Assert element is visible',
dependsOn: ['selector'],
exclusive: ['not-visible', 'count'],
}),
'not-visible': core_1.Flags.boolean({
description: 'Assert element is not visible',
dependsOn: ['selector'],
exclusive: ['visible', 'count'],
}),
value: core_1.Flags.string({
description: 'Assert input element value',
dependsOn: ['selector'],
exclusive: ['contains', 'equals', 'matches', 'count'],
}),
message: core_1.Flags.string({
char: 'm',
description: 'Custom error message if assertion fails',
}),
};
async run() {
const { flags } = await this.parse(Assert);
// For stored value assertions, we may not need Chrome connection
if (flags.stored && !flags.eval && !flags.selector) {
await this.assertStoredValue(flags);
return;
}
await this.connectToChrome(flags.port, flags.host, flags.launch, flags.profile, flags.headless, flags.verbose, flags.keepOpen);
if (!this.page) {
this.error('Failed to connect to Chrome');
}
await this.executeCommand(this.page, flags);
}
async executeCommand(page, flags) {
try {
if (flags.selector) {
await this.assertSelector(page, flags);
}
else if (flags.eval) {
await this.assertEvaluation(page, flags);
}
else if (flags.stored) {
await this.assertStoredValue(flags);
}
else {
this.error('Either --selector, --eval, or --stored must be specified');
}
}
catch (error) {
const message = flags.message || error.message;
this.error(`❌ Assertion failed: ${message}`);
}
}
async assertSelector(page, flags) {
const selector = flags.selector;
// Check if element exists
const count = await page.locator(selector).count();
if (count === 0 && !flags['not-visible']) {
throw new Error(`Element not found: ${selector}`);
}
// Handle count assertion
if (flags.count !== undefined) {
const expectedCount = parseInt(flags.count);
if (count !== expectedCount) {
throw new Error(`Expected ${expectedCount} elements, found ${count}`);
}
this.log(`✅ Assertion passed: Element count equals ${expectedCount}`);
return;
}
// Handle visibility assertions
if (flags.visible || flags['not-visible']) {
const locator = page.locator(selector).first();
const isVisible = count > 0 && await locator.isVisible();
if (flags.visible && !isVisible) {
throw new Error(`Element is not visible: ${selector}`);
}
if (flags['not-visible'] && isVisible) {
throw new Error(`Element is visible: ${selector}`);
}
this.log(`✅ Assertion passed: Element is ${flags.visible ? 'visible' : 'not visible'}`);
return;
}
// Get element content for text assertions
const element = page.locator(selector).first();
let content;
if (flags.value !== undefined) {
// Get input value
content = await element.inputValue() || '';
if (content !== flags.value) {
throw new Error(`Input value "${content}" does not equal "${flags.value}"`);
}
this.log(`✅ Assertion passed: Input value equals "${flags.value}"`);
return;
}
else {
// Get text content
content = await element.textContent() || '';
content = content.trim();
}
// Handle text assertions
this.assertContent(content, flags, `Element text`);
}
async assertEvaluation(page, flags) {
let result;
result = await (0, evaluation_js_1.safeEvaluate)(page, flags.eval);
// Convert result to string for comparison if needed
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
this.assertContent(resultStr, flags, `Evaluation result`, result);
}
async assertStoredValue(flags) {
const value = store_js_1.default.getStoredValue(flags.stored);
if (value === undefined) {
throw new Error(`No stored value found for: ${flags.stored}`);
}
const valueStr = typeof value === 'string' ? value : JSON.stringify(value);
this.assertContent(valueStr, flags, `Stored value "${flags.stored}"`, value);
}
assertContent(content, flags, prefix, rawValue) {
if (flags.contains) {
if (!content.includes(flags.contains)) {
throw new Error(`${prefix} "${content}" does not contain "${flags.contains}"`);
}
this.log(`✅ Assertion passed: ${prefix} contains "${flags.contains}"`);
}
else if (flags.equals) {
// For equals, also check raw value if available
const expectedStr = flags.equals;
if (content !== expectedStr && rawValue !== flags.equals) {
// Try numeric comparison if both can be parsed as numbers
const numContent = parseFloat(content);
const numExpected = parseFloat(expectedStr);
if (isNaN(numContent) || isNaN(numExpected) || numContent !== numExpected) {
throw new Error(`${prefix} "${content}" does not equal "${expectedStr}"`);
}
}
this.log(`✅ Assertion passed: ${prefix} equals "${flags.equals}"`);
}
else if (flags.matches) {
const regex = new RegExp(flags.matches);
if (!regex.test(content)) {
throw new Error(`${prefix} "${content}" does not match pattern "${flags.matches}"`);
}
this.log(`✅ Assertion passed: ${prefix} matches pattern "${flags.matches}"`);
}
else if (flags['greater-than']) {
const numContent = parseFloat(content);
const numThreshold = parseFloat(flags['greater-than']);
if (isNaN(numContent) || isNaN(numThreshold)) {
throw new Error(`Cannot compare non-numeric values: "${content}" > "${flags['greater-than']}"`);
}
if (numContent <= numThreshold) {
throw new Error(`${prefix} ${numContent} is not greater than ${numThreshold}`);
}
this.log(`✅ Assertion passed: ${prefix} (${numContent}) is greater than ${numThreshold}`);
}
else if (flags['less-than']) {
const numContent = parseFloat(content);
const numThreshold = parseFloat(flags['less-than']);
if (isNaN(numContent) || isNaN(numThreshold)) {
throw new Error(`Cannot compare non-numeric values: "${content}" < "${flags['less-than']}"`);
}
if (numContent >= numThreshold) {
throw new Error(`${prefix} ${numContent} is not less than ${numThreshold}`);
}
this.log(`✅ Assertion passed: ${prefix} (${numContent}) is less than ${numThreshold}`);
}
else {
// For boolean expressions without comparison
if (rawValue === false || rawValue === null || rawValue === undefined) {
throw new Error(`Expression evaluated to ${rawValue}`);
}
this.log(`✅ Assertion passed: ${prefix} exists${rawValue !== undefined ? ` (${content})` : ''}`);
}
// Log details if verbose
if (flags.verbose) {
this.logVerbose('Assertion details', {
type: prefix,
content: content,
rawValue: rawValue,
assertion: flags.contains ? 'contains' :
flags.equals ? 'equals' :
flags.matches ? 'matches' :
flags['greater-than'] ? 'greater-than' :
flags['less-than'] ? 'less-than' : 'exists'
});
}
}
}
exports.default = Assert;