{"id":13454893,"url":"https://github.com/nodeca/navit","last_synced_at":"2025-04-12T02:39:00.214Z","repository":{"id":29220338,"uuid":"32752005","full_name":"nodeca/navit","owner":"nodeca","description":"Simple client testing from your scripts","archived":false,"fork":false,"pushed_at":"2021-12-08T17:05:21.000Z","size":345,"stargazers_count":48,"open_issues_count":3,"forks_count":7,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-12T02:38:55.999Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/nodeca.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"open_collective":"puzrin","patreon":"puzrin"}},"created_at":"2015-03-23T18:49:16.000Z","updated_at":"2024-05-13T19:57:38.000Z","dependencies_parsed_at":"2022-07-24T16:17:17.557Z","dependency_job_id":null,"html_url":"https://github.com/nodeca/navit","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nodeca%2Fnavit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nodeca%2Fnavit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nodeca%2Fnavit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nodeca%2Fnavit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nodeca","download_url":"https://codeload.github.com/nodeca/navit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248507236,"owners_count":21115562,"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":[],"created_at":"2024-07-31T08:00:59.088Z","updated_at":"2025-04-12T02:39:00.196Z","avatar_url":"https://github.com/nodeca.png","language":"JavaScript","readme":"navit\n=====\n\n[![CI](https://github.com/nodeca/navit/actions/workflows/ci.yml/badge.svg)](https://github.com/nodeca/navit/actions/workflows/ci.yml)\n[![NPM version](https://img.shields.io/npm/v/navit.svg?style=flat)](https://www.npmjs.org/package/navit)\n\n\u003e Wrapper for [Electron](https://www.electronjs.org/) to simplify browser tests scripting.\n\n\nInstall\n-------\n\nNote, you need to install `electron` with this package, it is not included as\na dependency.\n\n```bash\nnpm install navit electron --save\n```\n\n\nExamples\n--------\n\n```js\nconst browser = require('./')({ timeout: 30000, engine: 'electron' });\n\nconst stack = []; // You can use lazy functions to pass data between stages,\n                  // but arrays have more compact notation.\n\ntry {\n  await browser\n    .open('https://dev.nodeca.com')\n    .wait(() =\u003e {\n      try { return window.NodecaLoader.booted; } catch (__) { return false; }\n    })\n    .get.url(stack)\n    .click('forum-category__content:first forum-section__title-link')\n    .wait(data =\u003e location.url !== data[data.length - 1], stack)\n    .test.exists('.forum-topiclines')\n    .close();\n\n  console.log('Succeeded');\n} catch (err) {\n  console.log(err));\n}\n```\n\nAlso look files in [test folder](https://github.com/nodeca/navit/tree/master/test).\nThose are real examples how to use `navit` with `mocha`.\n\n\nAPI\n---\n\n1. All methods are chainable.\n2. Methods, marked with `+` have direct aliases without namespace.\n3. Chain is then-able. You can apply `await` anytime, to execute stacked commands.\n4. Almost everywhere `String` \u0026 `Number` params can be defined as functions for\n   lazy evaluation.\n\n__Known limitations:__\n\nSome methods like `.get.evaluate()` allow to pass params to evaluated functions.\n`navit` uses function's `.length` property, to properly detect params count,\nbecause tailing callbacks are optional. That means, such functions must have\nexplicit parameters list in definition, and you must pass exactly the same\nparams count as defined. We decided, it's not a big price for nice API.\n\nElectron is NOT headless. To run your script in headless environment,\nyou should [xvfb](https://github.com/electron/electron/blob/master/docs/tutorial/testing-on-headless-ci.md).\n\n\n### new Navit(options, engineOpts)\n\n__options__ (not mandatory):\n\n- `inject`: Array of scripts (file paths) to inject after every page load\n  (`[ require.resolve('jquery/dist/jquery') ]`).\n- `timeout`: Page load and `.wait()` timeout, default `5000ms`.\n- `prefix`: url prefix for `.open()` and `.post()`, default empty string.\n- `engine`: optional, engine driver to use. Only `electron` is available for now.\n- `enginePath`: optional, direct path to browser engine. Don't use without\n   need, it should be autodetected via `electron` package.\n\n__engineOpts__ (not mandatory, camelCase):\n\nSee https://www.electronjs.org/docs/api/command-line-switches.\nYou can pass any options, supported by browser engine. Option names should be\nin camelCase.\n\n\n### Actions: `.do.*()`\n\nNavigation:\n\n- \\+ `.do.open(url [, options])`\n  - options:\n    - `method` - get(default)|post|put|delete|head\n    - `data`\n    - `headers`\n- \\+ `.do.post(url, data[, options])` - shortcut to `.open`, for convenience;\n  `method` is set to `post`, and `data` is forwarded to `options.data`.\n- \\+ `.do.back()`\n- \\+ `.do.forward()`\n- \\+ `.do.reload()`\n\nDOM:\n\n- \\+ `.do.click(selector)` - click on an element matching `selector`\n- \\+ `.do.type(selector, text)` - type `text` into input or contenteditable\n- \\+ `.do.clear(selector)` - clear input value (supports contenteditable)\n- \\+ `.do.check(selector)` - toggles checkbox or radio\n- \\+ `.do.select(selector, option)` - selects an `option` in dropdown\n- \\+ `.do.upload(selector, path)` - selects a file in `input[type=\"file\"]`\n- \\+ `.do.fill(selector, obj [, submit])` - fill out a form (same as\n  [fill](http://docs.casperjs.org/en/latest/modules/casper.html#fill)\n  in CasperJS)\n- \\+ `.do.scrollTo(top, left)` - executes `window.scrollTo(top, left)`\n- \\+ `.do.inject([type, ] file)` - appends a script or stylesheets from external `file` to page, `type` can be one of `js` or `css` (default type is `js`).\n\nWaiting:\n\n- \\+ `.do.wait()` - wait until page ready; can be useful after `click`, `back`\n  and `forward` actions (`open` and `reload` track progress for html pages\n  automatically)\n- \\+ `.do.wait(delay)` - pause for `delay` milliseconds\n- \\+ `.do.wait(selector [, timeout])` - wait until selector available\n- \\+ `.do.wait(fn [, params..., timeout])` - evaluate function in cycle, until\n  returns `true`.\n\n\n### Get data/params: `.get.*()`\n\nAll functions, passed to `.get.*`, can be sync (with 1 param) or async (with 2\nparams). If function returns not falsy type of result (usually a `Error`) or\nthrows exception, chain will be terminated. That can be used to create complex\ntest conditions.\n\n- \\+ `.get.title(fn)`\n- \\+ `.get.url(fn)`\n- \\+ `.get.count(selector, fn)`\n- \\+ `.get.text(selector, fn)`\n- \\+ `.get.html([selector,] fn)` - when no selector given, returns full page html.\n- \\+ `.get.attribute(selector, attribute, fn)`\n- \\+ `.get.value(selector, fn)` - for input/selector fields, returns field value.\n- `.get.evaluate(fnToEval [, params, fn])` - evaluate function on client with optional params.\n  Returned result can be processed on server, if handler set.\n- `.get.status(fn)`\n- `.get.body(fn)`\n- `.get.headers(fn)` - return server reply headers. Note, testing\n   method is not \"symmetric\" - use `.test.header(name, ...)`.\n- `.get.cookies(fn)` (no pair in `.test.*`)\n\nSugar:\n\n1. If you pass `Array` instead of `Function`, data will be pushed into it.\n2. If `fn` returns `Error` object (or anything else not falsy), chain will be\n   stopped with that value.\n3. If last param (`callback`) exists, chain will be finished as with `.run` method.\n\n\n### Set data/params: `.set.*()`\n\n- \\+ `.set.headers(obj)`\n- \\+ `.set.useragent(string)`\n- \\+ `.set.authentication(user, pass)`\n- \\+ `.set.viewport(width, height)`\n- \\+ `.set.zoom(scale)`\n- \\+ `.set.cookie(obj)`\n- \\+ `.set.cookie(name, value)` *If value not passed, cookie will be deleted.\n\n\n### Assertions: `.test.*()` \u0026 `test()`\n\nTests available for the most of `get.*` methods:\n\n- `.test.method_name(params..., value [, message)`\n- If value to compare is `RegExp`, then data is converted to `String`, and tested\n  to match provided regexp.\n- __Negative condition `.not` can be added to almost any test, before or after\n  method name.__\n\nAdditional:\n\n- \\+ `.test[.not].exists(selector [, message])`\n- \\+ `.test[.not].visible(selector [, message])`\n- \\+ `.test.evaluate(fn [params..., message])` - evaluate \u0026 die on any\n  result but `true`.\n\nSpecial sugar:\n\n- `.test(status_number [, message])`\n- `.test(body_rexexp [, message])`\n- `.test(header_name, string_or_regexp [, message])`\n\n\n### Tabs: `.tab.*()`\n\n- `.tab.open([url [, options]])` - create and switch to new tab. Run `.do.open(url, options)` if `url` specified\n- `.tab.count(fn)` - get tabs count (if you pass `Array`, value will be pushed into)\n- `.tab.switch(index)` - switch to tab by `index`\n- `.tab.close([index])` - close tab by `index` or close current tab if `index` not specified\n  - negative `index` address tab from the tail\n  - after all tabs closed, new one will be created automatically\n\n\n### Other\n\n- `.fn(function, params)` - local function execute. Function can be sync or\n  async (or return Promise). Params count should match function signature.\n- `.exit()` =\u003e `Promise` - tear down browser process. Note, browser will NOT be\n  closed until you do it explicit via this method or `.close()`.\n- `.close()` - similar to `.exit()` but stackable (will be executed in order\n  with other chained commends).\n- `.then(onSuccess, onFail)` - executes stacked commands.\n- `.screenshot([ selector|bounding_rect, type,] path)` - do screenshot\n- `.registerMethod(names, fn)` - add new method with given name(s) (`names`\n  can be string or array).\n- `.use(plugin [, params...])` - apply plugin.\n\n\n### Batches\n\n`navit` allows record sequence or commands to run it later with one call as\nmany times as you wish.\n\n```js\n// create\n.batch.create('init_page', function() {\n  this.\n    .wait(function () {\n      try {\n        return window.NodecaLoader.booted;\n      } catch (__) {}\n      return false;\n    });\n    .viewport(1600, 1200)\n    .inject(require.resolve('jquery/dist/jquery'))\n    .fn(function () {\n      console.log('Batch done.');\n    })\n})\n// run\n.batch('init_page')\n```\n\n\n### .afterOpen\n\nIf you assign function to this property, it will be called after any `.open`\nand `.reload` definition to stack additional commands. This is experimental\nfeature, that can be changed.\n\nSometime you may wish to do global setup for all opened pages. For example:\n\n- wait full page init, when it has dynamic scripts loader.\n- inject testing scripts from remote host (when you don't like to use global\n  option).\n\nYou can record your sequence to batch and automate it's injection after every\n`open` / `reload`. See example how we setup `navit` in `nodeca`:\n\n\n```js\n// Wait for nodeca scripts load and check status\n//\nnavit.batch.create('waitNodecaBooted', function () {\n  this\n    .wait(function () {\n      try {\n        return window.NodecaLoader.booted;\n      } catch (__) {}\n      return false;\n    })\n    .test.status(200);\n});\n\nnavit.afterOpen = function () {\n  this.batch('waitNodecaBooted');\n};\n```\n\n__Note__. `.afterOpen` is called on chain definition phase, not on execution\nphase. It's ~ equivalent of typing content manually in test body. That's why it\ndoesn't have callback to wait async operations - it's not needed.\n\n\n### Debug\n\nIf you assign environment variable `DEBUG` to `navit`, you will see debug message\nfor every action.\n\nOutput example for `DEBUG=navit mocha`:\n\n```\n...\nnavit do.open('http://localhost:17345/test/fixtures/do/type.html') +25ms\nnavit do.type('#type-test') +20ms\nnavit test.evaluate() +9ms\nnavit do.type('#contenteditable-test') +2ms\nnavit test.evaluate() +9ms\n  ✓ type (64ms)\n...\n```\n","funding_links":["https://opencollective.com/puzrin","https://patreon.com/puzrin"],"categories":["Packages","包","目录","Testing","Number"],"sub_categories":["Testing","测试","测试相关"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnodeca%2Fnavit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnodeca%2Fnavit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnodeca%2Fnavit/lists"}