A friend asked me last week: "I want to search Google from the command line and get the results. How hard can it be?"
"Easy," I said. "Just use Playwright."
He came back the next day: "I spent all afternoon reading Playwright docs and I still haven't gotten it working. I'm at 30 lines of code and I don't even have the search results yet."
I looked at his code:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://www.google.com');
await page.fill('input[name="q"]', 'node.js best practices');
await page.press('input[name="q"]', 'Enter');
await page.waitForSelector('#search');
const results = await page.$$eval('.g', (els) =>
els.map((el) => ({
title: el.querySelector('h3')?.textContent,
url: el.querySelector('a')?.href,
}))
);
console.log(results);
await browser.close();
})();
23 lines. Just to search Google.
And that code is fragile — Google's search result page structure changes frequently, and those CSS selectors could break at any moment. When they do, you have to open the browser, debug the DOM, find new selectors, update the code, and run it again.
"Isn't there a simpler way?"
There is. But before we get to that, we need to clear up a fundamental misunderstanding.
Test Framework ≠ Automation Tool
This is a distinction most people miss. Playwright, Selenium, Cypress — they are all test frameworks, not automation tools.
A test framework's core design goals are:
- Assertion-driven: Verify that page behavior matches expectations
- Report generation: Produce HTML/JSON test reports
- Parallel execution: Run tests across multiple browsers and devices simultaneously
- CI integration: Work within GitHub Actions, Jenkins, and other pipelines
- Debugging tools: Trace viewers, screenshot diffing, video playback
These features matter when you're writing tests. But if you just want to "search Google and get results"?
You don't need assertions. You don't need test reports. You don't need parallel execution. You need: open page → extract data → move on.
An automation tool's core design goals are completely different:
- CLI-driven: One command, one operation
- One-shot execution: No need for persistent test suites
- Pipe-friendly: Output can be processed by other commands
- Fast feedback: Seconds, not minutes
- Script-friendly: Embed directly in bash scripts or cron jobs
In other words: test frameworks care about "is it correct?" Automation tools care about "is it fast?"
Three Approaches, One Task
Let's compare three approaches using the same task: Google search for "node.js tutorial", extract the top 10 results.
Approach 1: Playwright
// search.js
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://www.google.com/search?q=node.js+tutorial');
await page.waitForSelector('#search');
const results = await page.$$eval('.g', (elements) =>
elements.slice(0, 10).map((el) => ({
title: el.querySelector('h3')?.textContent || '',
url: el.querySelector('a')?.href || '',
snippet: el.querySelector('.VwiC3b')?.textContent || '',
}))
);
console.log(JSON.stringify(results, null, 2));
await browser.close();
})();
To run:
npm install playwright # Install dependencies, download browser (~300MB)
node search.js # Run
Cost: 300MB browser download + 23 lines of code + ongoing selector maintenance.
Approach 2: Selenium
# search.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import json
driver = webdriver.Chrome()
driver.get("https://www.google.com/search?q=node.js+tutorial")
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "search"))
)
results = []
elements = driver.find_elements(By.CSS_SELECTOR, ".g")[:10]
for el in elements:
title = el.find_element(By.CSS_SELECTOR, "h3").text if el.find_elements(By.CSS_SELECTOR, "h3") else ""
url = el.find_element(By.CSS_SELECTOR, "a").get_attribute("href") if el.find_elements(By.CSS_SELECTOR, "a") else ""
snippet = el.find_element(By.CSS_SELECTOR, ".VwiC3b").text if el.find_elements(By.CSS_SELECTOR, ".VwiC3b") else ""
results.append({"title": title, "url": url, "snippet": snippet})
print(json.dumps(results, indent=2))
driver.quit()
To run:
pip install selenium # Install dependency
# Also need to download ChromeDriver separately
python search.py # Run
Cost: Install dependencies + download ChromeDriver + 27 lines of code + ongoing selector maintenance.
Approach 3: CLI Tool
xbrowser google search "node.js tutorial" --json --limit 10
To run:
npx xbrowser google search "node.js tutorial" --json --limit 10
Cost: One command.
Output:
[
{
"title": "Node.js Tutorial - W3Schools",
"url": "https://www.w3schools.com/nodejs/",
"snippet": "Learn Node.js with our comprehensive tutorial..."
},
{
"title": "The Node.js Handbook - FreeCodeCamp",
"url": "https://www.freecodecamp.org/news/the-nodejs-handbook/",
"snippet": "This handbook is a getting-started guide to Node.js..."
}
]
No browser to install. No code to write. No selectors to maintain.
The Comparison Table
| Dimension | Playwright | Selenium | CLI Tool |
|---|---|---|---|
| Purpose | Test framework | Test framework | Automation tool |
| Install size | ~300MB | ~150MB + Driver | ~50MB |
| Code per operation | 20-50 lines | 20-50 lines | 1 line |
| Learning curve | High (Page Objects, Locators, Contexts) | High (WebDriver protocol, wait strategies) | Low (command-line args) |
| Selector maintenance | Manual | Manual | Built-in |
| Output format | Raw (format yourself) | Raw (format yourself) | Native JSON |
| Pipe support | Requires extra handling | Requires extra handling | Native |
| Best for | Regression tests, E2E tests | Compatibility tests, cross-browser | Daily automation, data extraction |
| Target audience | QA engineers | QA engineers | Developers, ops, content creators |
| CI integration | Built-in | Built-in | Via shell scripts |
| Assertion capability | Powerful | Powerful | None (use jq/awk) |
They're Not Replacements — They're Complements
I'm not here to knock Playwright or Selenium. They're top-tier tools in their domains.
If you're doing regression testing — verifying 50 pages worth of core functionality before every release — Playwright is the right choice. You need assertions. You need test reports. You need the Trace Viewer to debug failing cases.
If you're doing compatibility testing — making sure your product works on Chrome, Firefox, and Safari — Selenium's cross-browser capabilities are unmatched.
But if you're doing any of these:
- Checking Google rankings on a schedule
- Scraping web data in bulk
- Submitting sitemaps automatically
- Monitoring competitor price changes
- Cross-posting content to multiple platforms
- Quickly searching and getting structured results from the command line
You don't need a test framework. You need an automation tool.
Using a test framework for automation is like using a cannon to kill a mosquito. It works, but the cost is way too high.
When to Use What
Here's a simple heuristic:
You need to verify "is it correct?" → Test framework
You need to quickly "get something done" → Automation tool
More specifically:
Use Playwright / Selenium when:
- E2E testing: Ensure signup flows and payment flows don't break
- Regression testing: Run full suite before every release
- Visual regression: Screenshot comparison to detect UI changes
- Performance testing: Measure page load times and interaction latency
- CI/CD integration: Run automatically in your pipeline
Use CLI automation tools when:
- Data extraction: Pull structured data from web pages
- SEO operations: Check rankings, submit links, verify indexing
- Content management: Bulk publishing, scheduled publishing
- Monitoring and alerting: Periodic page status checks, price change detection
- Daily chores: Search, screenshot, form filling, downloading
With shell scripts + CLI tools, you can even do things test frameworks aren't designed for:
# Monitor competitor price every hour, auto-alert on Slack when it drops
watch -n 3600 'xbrowser crawl "$COMPETITOR_URL" --extract ".price" \
| xargs -I{} bash -c "[[ {} < 99 ]] && echo Price dropped to {} | slacksend"'
# Daily ranking report at 9 AM, generate Markdown
0 9 * * * for kw in "js tutorial" "node guide" "react tips"; do \
echo "### $kw"; xbrowser google search "$kw" --json --limit 3 \
| jq -r ".[] | \"- [\(.title)](\(.url))\""; done > /tmp/rank-report.md
Playwright and Selenium can't do this — not because it's technically impossible, but because it's not what they were designed for. They weren't built for "quickly execute one-off tasks."
My Recommendation
If you're a developer whose daily work involves browser operations but not testing:
- Use CLI tools for 80% of your daily browser tasks
- Introduce Playwright only when you genuinely need testing
- Don't use Playwright for things CLI should handle — you'll just create maintenance burden
If you're a QA engineer whose primary job is testing:
- Playwright or Selenium should be your primary tools
- But for occasional non-testing browser operations, CLI tools save a lot of time
- They complement each other. No conflict.
If you're an ops / content creator / SEO engineer whose daily work is repetitive browser operations:
- CLI automation tools are your best friend
- Combined with cron and shell scripts, you can automate your entire daily workflow
- No need to learn a test framework. The command line is enough.
If you're looking for a CLI browser automation tool, xbrowser is worth checking out. It wraps Playwright's browser control capabilities into a command-line interface — Google search, web scraping, SEO ping, all done in one line, with native JSON output and pipe support. Built for daily automation workflows and cron-friendly scheduled tasks.
Top comments (0)