{"id":16293014,"url":"https://github.com/kimmobrunfeldt/squint","last_synced_at":"2025-03-20T03:31:00.940Z","repository":{"id":66162309,"uuid":"395368937","full_name":"kimmobrunfeldt/squint","owner":"kimmobrunfeldt","description":"Makes visual reviews of web app releases easy.","archived":false,"fork":false,"pushed_at":"2021-08-16T21:40:30.000Z","size":1990,"stargazers_count":30,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-02-28T22:54:44.825Z","etag":null,"topics":["crawl","diff","node","puppeteer","screenshot","testing","visual","visual-diff"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kimmobrunfeldt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-12T15:45:22.000Z","updated_at":"2025-01-26T23:12:21.000Z","dependencies_parsed_at":"2023-02-21T00:16:31.232Z","dependency_job_id":null,"html_url":"https://github.com/kimmobrunfeldt/squint","commit_stats":{"total_commits":80,"total_committers":1,"mean_commits":80.0,"dds":0.0,"last_synced_commit":"577727e62115557ffcd884ab50332750933e08ac"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kimmobrunfeldt%2Fsquint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kimmobrunfeldt%2Fsquint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kimmobrunfeldt%2Fsquint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kimmobrunfeldt%2Fsquint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kimmobrunfeldt","download_url":"https://codeload.github.com/kimmobrunfeldt/squint/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244047647,"owners_count":20389206,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crawl","diff","node","puppeteer","screenshot","testing","visual","visual-diff"],"created_at":"2024-10-10T20:09:43.943Z","updated_at":"2025-03-20T03:31:00.932Z","avatar_url":"https://github.com/kimmobrunfeldt.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"[![Status badge](https://github.com/kimmobrunfeldt/squint/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/kimmobrunfeldt/squint/actions?query=branch%3Amain)\n\n[![NPM](https://nodei.co/npm/squint-cli.png?compact=true)](https://npmjs.org/package/squint-cli)\n\n# Squint\n\n\u003e Makes visual reviews of web app releases easy.\n\n![Example terminal usage](https://raw.githubusercontent.com/kimmobrunfeldt/squint/main/docs/terminal.gif)\n\n`squint compare https://prod.myapp.com https://beta.myapp.com` uses Puppeteer to:\n\n* automatically crawl all url paths from the beta version *(see [this issue](https://github.com/kimmobrunfeldt/squint/issues/2))*\n* take screenshots of each page from prod and beta versions of the app\n* output all diff images for pages that had visual differences\n\nThat's the main intended use case. The diffs will likely\nhave false positives due to async loading, animations, and different data.\nThat's ok, the main intention is not to be a full solution, but rather a light-weight\nalternative.\n\nFor most production setups, I'd recommend using [Percy](https://percy.io/) instead.\nThat said, CLI flags have been designed customization in mind: you can for example run\ncustom JS code before Puppeteer takes a screenshot.\n\n**Goodies:**\n\n* [Puppeteer](https://github.com/puppeteer/puppeteer) under the hood\n* Smart defaults but [highly configurable](#usage).\n\n    The goal is to provide most convenience flags via CLI, but allow flexible JS options for advanced tricks.\n\n* Supports [connecting to your local Chrome](#connect-to-a-chrome-session), with its existing logins and sessions. No need to deal with cookies in code.\n* Tested on all platforms: macOS, Windows, and Linux\n\n![Example image of diff](https://raw.githubusercontent.com/kimmobrunfeldt/squint/main/docs/diff.png)\n\n## Known limitations\n\nThe path structure needs to be 1-to-1 match between the two sites. If you have preview builds for example under\n`https://prod.myapp.com/previews/123/...`, this won't work automatically. The workaround is to use a [proxy](https://www.npmjs.com/package/http-proxy-cli):\n\n```bash\nhttp-proxy -p 8080 https://prod.myapp.com/previews/123/ \u0026\nPID=$!\nsquint compare https://prod.myapp.com/ http://localhost:8080\n\nkill $PID\n```\n\n## Install\n\n`npm i -g squint-cli`\n\n## Usage\n\n*Auto-generated*\n\n```bash\n  EXAMPLES\n\n      Compare current production to beta. The whole site is automatically crawled.\n      $ squint compare https://example.com https://beta.example.com\n\n      Crawl all paths from beta site and pipe output to a file. The crawler only follows site-internal links.\n      $ squint crawl https://beta.example.com \u003e paths.txt\n\n      Get screenshot of a page.\n      $ squint screenshot https://beta.example.com\n\n      Get screenshot of a single element in a page.\n      $ squint screenshot --selector 'div' https://beta.example.com\n\n      Compare current production to beta, but use an existing file of paths.\n      $ squint compare --paths-file paths.txt https://example.com https://beta.example.com\n\n      Compare a single page.\n      $ squint compare --single-page https://example.com/about https://beta.example.com/about\n\n      Compare a single element with a selector.\n      $ squint compare --selector '#logo' https://example.com https://beta.example.com\n\n      Compare a single element, but use JS to dig an element from the page. (page: Puppeteer.Page) =\u003e HTMLElement\n      $ squint compare --selector-js '(page) =\u003e page.$(\"#logo\")' https://example.com https://beta.example.com\n\n  COMMON OPTIONS\n\n      --help                       Shows this help message\n      --puppeteer-launch-mode      Options: launch, connect. Default: launch\n      --puppeteer-launch-options   Puppeteer .launch or .connect options in JS. Default: {\"headless\":true}\n      --puppeteer-page-pool-max    Maximum pages to use at the same time with Puppeteer. Default: 10\n      --include-hash               When enabled, URL hashes are not ignored when crawling. Default: false\n      --include-search-query       When enabled, URL search queries are not ignored when crawling. Default: false\n      --should-visit               Custom JS function that can limit which links the crawler follows.\n                                   This is an AND filter on top of all other filters.\n                                   (\n                                     urlToVisit: url.URL,\n                                     hrefDetails: { currentUrl: string, href: string },\n                                     visited: Set\u003cstring\u003e,\n                                     config: Config\n                                   ) =\u003e boolean\n      --trailing-slash-mode        Options: preserve, remove, add. Default: preserve\n\n  COMPARE \u0026 SCREENSHOT\n\n      -w --width             Viewport width for Puppeteer. Default: 1280\n      -h --height            Viewport height for Puppeteer. Default: 800\n      --paths-file           File of URL paths. One path per line.\n      --selector             Takes screenshot from a single element. Selector for document.querySelector.\n                             page.waitForSelector is called to ensure the element is visible.\n      --selector-js          Takes screenshot from a single element. Selector as JS function that digs an element.\n                             (page: Puppeteer.Page) =\u003e HTMLElement\n      --screenshot-options   Puppeteer .screenshot options in JS. Overrides other options.\n      --after-goto           Custom JS function that will be run after Puppeteer page.goto has been called.\n                             (page: Puppeteer.Page) =\u003e Promise\u003cvoid\u003e\n      --after-page           Custom JS function that will be run after Puppeteer page has been created.\n                             (page: Puppeteer.Page) =\u003e Promise\u003cvoid\u003e\n\n  COMPARE\n\n      --out-dir              Output directory for images. Default: .squint\n      --single-page          Disable automatic crawling. Only take a screenshot from single page.\n      -o --out-file          Relevant in only in single-page mode. Output file for the diff image.\n      --save-all             Saves all diff image files, even if there are zero differences.\n\n  SCREENSHOT\n\n      -o --out-file          Output file for the screenshot\n\n  CRAWL\n\n      --max-depth            Maximum depth of links to follow. Default: Infinity\n\n\n```\n\n## Tips \u0026 tricks\n\nGot a trick? Submit a PR!\n\n### Increase Puppeteer timeouts\n\n```bash\nsquint screenshot https://github.com/kimmobrunfeldt/squint --after-page \"async (page) =\u003e page.setDefaultTimeout(60000)\"\n```\n\nYou can also use `page.setDefaultNavigationTimeout(60000)` to only change navigation timeouts.\n\n### Interact with page before screenshot\n\nClick \"I agree\" button to hide ToS popup before taking a screenshot. *Beware of character escaping, it's fragile.*\n\n```bash\nsquint screenshot https://google.com --puppeteer-launch-options '{ headless: false }' --after-goto 'async (page) =\u003e {\n  const [button] = await page.$x(`//button[contains(., \"I agree\")]`);\n  await button.click();\n}'\n```\n\n### Disable full page screenshot\n\nTake screenshot from the current viewport:\n\n```bash\nsquint screenshot https://github.com/kimmobrunfeldt/squint --screenshot-options '{ fullPage: false }';\n```\n\n### Enable very verbose Puppeteer logging\n\n```bash\nDEBUG=\"puppeteer:* squint screenshot https://example.com\n```\n\n### Disable headless mode\n\n**Warning:** It seems that Puppeteer element screenshot works differently when running with `headless: false`. Use with caution!\n\n```bash\nsquint screenshot --puppeteer-launch-options '{ headless: false }' https://example.com\n```\n\n### Connect to a Chrome session\n\n*This way you can reuse existing sessions and logins. No need to do difficult setup with cookies.*\n\n1. Close your Chrome process (unless you already have remote debugging enabled).\n2. Launch Chrome with a remote debugger enabled\n\n    In macOS: `/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222`\n\n3. `squint screenshot --puppeteer-launch-mode connect --puppeteer-launch-options '{ browserURL: \"http://localhost:9222\" }' https://example.com`\n\n### Customize crawler\n\n`--should-visit` can be useful if your page has a ton of blog posts:\n\n```bash\nsquint crawl https://mysite.com --should-visit '(urlToVisit, hrefDetails, visited) =\u003e {\n  // Only visit 2 blog posts at max, assuming your site uses structure: /blog/posts/:id\n  const isBlogPost = (url) =\u003e url.includes(\"/blog/posts/\");\n\n  if (!isBlogPost(urlToVisit.toString())) {\n    // Visit if something else than blog post\n    return true;\n  }\n\n  // If the visited list has 0 or 1 visits to a post,\n  // we should still visit one post.\n  return [...visited].filter(url =\u003e isBlogPost(url)).length \u003c= 1;\n}'\n```\n\n`urlToVisit` is a [WHATWG URL](https://nodejs.org/api/url.html#url_new_url_input_base) object.\nThe flag also works for `compare` command.\n\n### Keep crawler in specific parts of a site\n\nStart crawling from a nested path, and use `--should-visit` to limit crawling.\n\n```bash\nsquint crawl https://github.com/kimmobrunfeldt/squint/ --should-visit '(urlToVisit) =\u003e {\n  const isTree = urlToVisit.pathname.startsWith(\"/kimmobrunfeldt/squint/tree/main\");\n  const isBlob = urlToVisit.pathname.startsWith(\"/kimmobrunfeldt/squint/blob/main\");\n  return isTree || isBlob;\n}'\n```\n\n### Debugging JS arguments\n\nYou can use regular JS console.log for the incoming parameters, for example for `--should-visit`:\n\n```bash\nsquint crawl https://myapp.com --should-visit '(...args) =\u003e console.log(...args) || true'\n```\n\n### Disable headless mode\n\n```bash\nsquint screenshot --puppeteer-launch-options '{ headless: false }' https://example.com\n```\n\n## Maintenance tasks\n\n### Debugging tests\n\n```\nDEBUG_TESTS=true npm test\n```\n\nThere's also `DEBUG_PUPPETEER=true` that launches Chrome with the UI visible. It seems that Puppeteer element screenshot works differently when running with `headless: false`. Use with caution!\n\n\n### Generating baseline screenshots for tests\n\n*Make sure the images are small because they are stored in git.*\n\nRun `bash tools/populate-test-data.sh` to re-generate PNG images for test cases.\nThese images are the baseline, and considered to be correct results.\n\n### Making a new release\n\n```\nbash release.sh\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkimmobrunfeldt%2Fsquint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkimmobrunfeldt%2Fsquint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkimmobrunfeldt%2Fsquint/lists"}