chromancer
Version:
A powerful command-line interface for automating Chrome browser using Playwright. Perfect for web scraping, automation, testing, and browser workflows.
121 lines (120 loc) • 5.14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@oclif/core");
const base_js_1 = require("../base.js");
const selectors_js_1 = require("../utils/selectors.js");
const errors_js_1 = require("../utils/errors.js");
class Hover extends base_js_1.BaseCommand {
static description = 'Hover over an element to trigger hover states and tooltips';
static examples = [
'<%= config.bin %> <%= command.id %> "#menu-item"',
'<%= config.bin %> <%= command.id %> ".dropdown-toggle" --duration 2000',
'<%= config.bin %> <%= command.id %> "button.info" --wait-for ".tooltip"',
];
static flags = {
...base_js_1.BaseCommand.baseFlags,
duration: core_1.Flags.integer({
char: 'd',
description: 'Duration to keep hovering in milliseconds',
default: 0,
}),
'wait-for': core_1.Flags.string({
description: 'Wait for element to appear after hovering',
}),
position: core_1.Flags.string({
description: 'Hover position relative to element (format: x,y)',
}),
force: core_1.Flags.boolean({
description: 'Force hover even if element is obscured',
default: false,
}),
};
static args = {
selector: core_1.Args.string({
required: true,
description: 'CSS selector of element to hover over',
}),
};
async run() {
const { args, flags } = await this.parse(Hover);
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, args.selector, flags);
}
async executeCommand(page, selector, flags) {
try {
this.logVerbose(`Looking for element: ${selector}`);
// Wait for element to be present and visible
await (0, selectors_js_1.waitForElement)(page, selector, {
state: 'visible',
timeout: 30000
});
// Build hover options
const hoverOptions = {
force: flags.force,
};
// Parse position if provided
if (flags.position) {
const [x, y] = flags.position.split(',').map((n) => parseInt(n.trim(), 10));
if (!isNaN(x) && !isNaN(y)) {
hoverOptions.position = { x, y };
this.logVerbose('Hover position', { x, y });
}
else {
this.warn('Invalid position format, ignoring position flag');
}
}
this.log(`🎯 Hovering over element: ${selector}`);
// Move mouse to element
await page.hover(selector, hoverOptions);
this.log(`✅ Hovered over element: ${selector}`);
// Wait for secondary element if specified
if (flags['wait-for']) {
this.log(`⏳ Waiting for: ${flags['wait-for']}`);
try {
await (0, selectors_js_1.waitForElement)(page, flags['wait-for'], {
state: 'visible',
timeout: 5000
});
this.log(`✅ Element appeared: ${flags['wait-for']}`);
}
catch (error) {
this.warn(`Element did not appear: ${flags['wait-for']}`);
}
}
// If duration specified, keep hovering
if (flags.duration > 0) {
this.logVerbose(`Keeping hover for ${flags.duration}ms`);
await new Promise(resolve => setTimeout(resolve, flags.duration));
}
// Log element info if verbose
if (flags.verbose) {
try {
const elementInfo = await page.$eval(selector, (el) => {
const rect = el.getBoundingClientRect();
const styles = window.getComputedStyle(el);
return {
tagName: el.tagName.toLowerCase(),
position: { x: rect.x, y: rect.y },
size: { width: rect.width, height: rect.height },
cursor: styles.cursor,
hasTitle: !!el.getAttribute('title'),
hasTooltip: !!el.getAttribute('data-tooltip') || !!el.getAttribute('data-bs-tooltip'),
};
});
this.logVerbose('Hovered element info', elementInfo);
}
catch {
// Element may have changed after hover
}
}
}
catch (error) {
const commandError = (0, errors_js_1.handleCommandError)(error, 'hover', selector);
this.error(commandError.message);
}
}
}
exports.default = Hover;