{"id":20456251,"url":"https://github.com/semperos/webdriver-logic","last_synced_at":"2025-10-20T09:01:48.149Z","repository":{"id":1822453,"uuid":"2746714","full_name":"semperos/webdriver-logic","owner":"semperos","description":"Logic programming API for testing web applications using clj-webdriver","archived":false,"fork":false,"pushed_at":"2013-02-07T13:45:28.000Z","size":322,"stargazers_count":68,"open_issues_count":0,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-26T21:02:55.080Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"rendrjs/rendr","license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/semperos.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2011-11-10T05:20:40.000Z","updated_at":"2024-08-07T05:19:30.000Z","dependencies_parsed_at":"2022-08-19T22:31:39.883Z","dependency_job_id":null,"html_url":"https://github.com/semperos/webdriver-logic","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semperos%2Fwebdriver-logic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semperos%2Fwebdriver-logic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semperos%2Fwebdriver-logic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semperos%2Fwebdriver-logic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/semperos","download_url":"https://codeload.github.com/semperos/webdriver-logic/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248661706,"owners_count":21141450,"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-11-15T11:21:58.026Z","updated_at":"2025-10-20T09:01:48.091Z","avatar_url":"https://github.com/semperos.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WebDriver Logic #\n\nThe WebDriver Logic library provides a \"mirror\" of the [clj-webdriver](https://github.com/semperos/clj-webdriver) API using relations (see the [core.logic](https://github.com/clojure/core.logic) library), including a powerful, declarative syntax for authoring your functional web tests and letting webdriver-logic make inferences about the state of your application.\n\n## Usage ##\n\n**Note:** This library is in the earliest stages of development. Feedback is welcome; use at your own risk.\n\nCheck out the ClojureConj talk : [Web Testing with Logic Programming ](http://www.youtube.com/watch?v=09zlcS49zL0)\n\n### Exploration ###\n\nHere's a simple example where we find the first element with a class of `footer_nav`:\n\n```clj\n(use 'webdriver-logic.core)\n(require '[clj-webdriver.core :as wd])\n(set-driver! {:browser :chrome} \"https://github.com\")\n\n(run 1 [q]\n  (attributeo q :class \"footer_nav\"))\n;=\u003e ({:webelement #\u003cTag: \u003cul\u003e, Text: GitHub  About  Blog  Features  Contact \u0026 Support  Training  ..., Class: footer_nav, Object: [[ChromeDriver: chrome on MAC (26459fb4e495c6bf086ea92acbaa7715)] -\u003e xpath: //*]\u003e})\n```\n\nOr how about all the footer navigation sections?\n\n```clj\n(run* [q]\n  (attributeo q :class \"footer_nav\"))\n;=\u003e\n;; ({:webelement\n;;   #\u003cTag: \u003cul\u003e, Text: GitHub  About  Blog  Features  Contact \u0026 Support  Training  ..., Class: footer_nav, Object: [[ChromeDriver: chrome on MAC (6140efaa871769f2b7baa8fa885ebabc)] -\u003e xpath: //*]\u003e}\n;;  {:webelement\n;;   #\u003cTag: \u003cul\u003e, Text: Clients  GitHub for Mac  GitHub for Windows  GitHub for Ecli..., Class: footer_nav, Object: [[ChromeDriver: chrome on MAC (6140efaa871769f2b7baa8fa885ebabc)] -\u003e xpath: //*]\u003e}\n;;  {:webelement\n;;   #\u003cTag: \u003cul\u003e, Text: Tools  Gauges: Web analytics  Speaker Deck: Presentations  G..., Class: footer_nav, Object: [[ChromeDriver: chrome on MAC (6140efaa871769f2b7baa8fa885ebabc)] -\u003e xpath: //*]\u003e}\n;;  {:webelement\n;;   #\u003cTag: \u003cul\u003e, Text: Documentation  GitHub Help  Developer API  GitHub Flavored M..., Class: footer_nav, Object: [[ChromeDriver: chrome on MAC (6140efaa871769f2b7baa8fa885ebabc)] -\u003e xpath: //*]\u003e})\n```\n\nHow about the first three elements on the page that have a legitimate `id` attribute?\n\n```clj\n(run 3 [q]\n  (fresh [an-element a-value]\n    (attributeo an-element :id a-value)\n    (!= a-value nil)\n    (!= a-value \"\")\n    (== q [a-value an-element])))\n;=\u003e\n;; ([\"gauges-tracker\"\n;;   {:webelement\n;;    #\u003cTag: \u003cscript\u003e, Id: gauges-tracker, Source: https://secure.gaug.es/track.js, Object: [[ChromeDriver: chrome on MAC (1fc632cc0ded7fc2c7fa1db418329876)] -\u003e xpath: //*]\u003e}]\n;;  [\"wrapper\"\n;;   {:webelement\n;;    #\u003cTag: \u003cdiv\u003e, Text: Signup and Pricing  Explore GitHub  Features  Blog  Sign in ..., Id: wrapper, Object: [[ChromeDriver: chrome on MAC (1fc632cc0ded7fc2c7fa1db418329876)] -\u003e xpath: //*]\u003e}]\n;;  [\"header\"\n;;   {:webelement\n;;    #\u003cTag: \u003cdiv\u003e, Text: Signup and Pricing  Explore GitHub  Features  Blog  Sign in, Id: header, Class: true clearfix, Object: [[ChromeDriver: chrome on MAC (1fc632cc0ded7fc2c7fa1db418329876)] -\u003e xpath: //*]\u003e}])\n```\n\nAnd if we limit the search domain to a sub-set of elements on the page (in this case, only `div` elements):\n\n```clj\n(binding [*search-domain* {:xpath \"//div\"}]\n  (run 3 [q]\n       (fresh [an-element a-value]\n              (attributeo an-element :id a-value)\n              (!= a-value nil)\n              (!= a-value \"\")\n              (== q [a-value an-element]))))\n;=\u003e\n;; ([\"wrapper\"\n;;   {:webelement\n;;    #\u003cTag: \u003cdiv\u003e, Text: Signup and Pricing  Explore GitHub  Features  Blog  Sign in ..., Id: wrapper, Object: [[ChromeDriver: chrome on MAC (1fc632cc0ded7fc2c7fa1db418329876)] -\u003e xpath: //div]\u003e}]\n;;  [\"header\"\n;;   {:webelement\n;;    #\u003cTag: \u003cdiv\u003e, Text: Signup and Pricing  Explore GitHub  Features  Blog  Sign in, Id: header, Class: true clearfix, Object: [[ChromeDriver: chrome on MAC (1fc632cc0ded7fc2c7fa1db418329876)] -\u003e xpath: //div]\u003e}]\n;;  [\"footer-push\"\n;;   {:webelement\n;;    #\u003cTag: \u003cdiv\u003e, Id: footer-push, Object: [[ChromeDriver: chrome on MAC (1fc632cc0ded7fc2c7fa1db418329876)] -\u003e xpath: //div]\u003e}])\n```\n\nPretty simple - you could do that with regular CSS or XPath queries. One could argue, however, that even at this simple point the declarative nature of `run*` is easier to follow and reason about than a series of explicit `find-element`, `filter` or `remove` calls.\n\nLet's make the inference work harder for us. Are there two links included in both the header and footer that have the same `href` value?\n\n```clj\n(binding [*search-domain* {:xpath \"//a\"}\n          *child-search-domain* {:xpath \".//a\"}]\n  (run 2 [q]\n     (fresh [header-el footer-el the-href-value]\n            (attributeo header-el :href the-href-value)\n            (attributeo footer-el :href the-href-value)\n            (!= the-href-value nil)\n            (!= the-href-value \"\")\n            (childo header-el (wd/find-element driver {:id \"header\"}))\n            (childo footer-el (wd/find-element driver {:id \"footer\"}))\n            (== q [the-href-value header-el footer-el]))))\n;=\u003e\n;; ([\"https://github.com/features\"\n;;   {:webelement\n;;    #\u003cTag: \u003ca\u003e, Text: Features, Href: https://github.com/features, Object: [[ChromeDriver: chrome on MAC (1fc632cc0ded7fc2c7fa1db418329876)] -\u003e xpath: //a]\u003e}\n;;   {:webelement\n;;    #\u003cTag: \u003ca\u003e, Text: Features, Href: https://github.com/features, Object: [[ChromeDriver: chrome on MAC (1fc632cc0ded7fc2c7fa1db418329876)] -\u003e xpath: //a]\u003e}]\n;;  [\"https://github.com/blog\"\n;;   {:webelement\n;;    #\u003cTag: \u003ca\u003e, Text: Blog, Href: https://github.com/blog, Object: [[ChromeDriver: chrome on MAC (1fc632cc0ded7fc2c7fa1db418329876)] -\u003e xpath: //a]\u003e}\n;;   {:webelement\n;;    #\u003cTag: \u003ca\u003e, Text: Blog, Href: https://github.com/blog, Object: [[ChromeDriver: chrome on MAC (1fc632cc0ded7fc2c7fa1db418329876)] -\u003e xpath: //a]\u003e}])\n```\n\nYou'll notice again the binding of `*search-domain*` and `*child-search-domain*` to a subset of all anchor elements on the page. Though this is not necessary for the program to run, it drastically improves performance. Relations like `attributeo` have to traverse all the elements on the page to find an answer, which for Selenium-WebDriver means creating lots of objects.\n\nYou can `flash` these elements to convince yourself that the above works:\n\n```clj\n(require '[clj-webdriver.element :as el])\n\n(doseq [[_ h f] (binding [*search-domain* {:xpath \"//a\"}\n                          *child-search-domain* {:xpath \".//a\"}]\n                  (run 2 [q]\n                    (fresh [header-el footer-el the-href-value]\n                      (attributeo header-el :href the-href-value)\n                      (attributeo footer-el :href the-href-value)\n                      (!= the-href-value nil)\n                      (!= the-href-value \"\")\n                      (childo header-el (wd/find-element driver {:id \"header\"}))\n                      (childo footer-el (wd/find-element driver {:id \"footer\"}))\n                      (== q [the-href-value header-el footer-el]))))]\n  (wd/flash h)\n  (wd/flash f))\n```\n\nLarger and more meaningful examples forthcoming.\n\n### Writing Your Tests ###\n\nGiven that core.logic returns values that can be consumed by \"regular\" functions, it's not hard to imagine how to compose tests against those values. But in the spirit of [Prolog unit tests](http://www.swi-prolog.org/pldoc/package/plunit.html), webdriver-logic provides a handful of macros that wrap clojure.test's `is` macro for common Logic Programming cases:\n\n * `s` - Succeeds if a single value is returned from the relation (deterministic behavior)\n * `s+` - Succeeds if more than one value is returned from the relation (non-deterministic behavior)\n * `s-as` - Succeeds if its first parameter `=` the value returned from the relation\n * `s-includes` - Succeeds if its first parameter contains values that are included in the value returned from the relation\n * `s?` - Succeeds if its first parameter, a predicate, returns true when passed the **seq of values** returned from the relation\n * `u` - Succeeds if the relation fails\n \nRemember that core.logic `run*` and friends always return a seq of zero or more values.\n\nFor examples of these in action, please see this library's test suite.\n\n### Contributing ###\n\nThis library is currently in the earliest stages of development. Pull requests against master are welcome, preferably authored in a feature branch.\n\nRun the tests with Leiningen:\n\n```\nlein test\n```\n\n*Note:* If you just want to run the example app that webdriver-logic uses for its testing purposes, do the following:\n\n * Open a terminal and run `lein repl` or at the root of this project\n * Evaluate `(use 'webdriver-logic.test.example-app.core 'ring.adapter.jetty)`\n * Evaluate `(defonce my-server (run-jetty #'routes {:port 5744, :join? false}))` (make sure your port selection doesn't conflict with actual test runs)\n\n### Logic Programming Materials ###\n\n#### Clojure/Lisp ####\n \n * [The Reasoned Schemer](http://mitpress.mit.edu/catalog/item/default.asp?ttype=2\u0026tid=10663) (also [available in Kindle format](http://www.amazon.com/The-Reasoned-Schemer-ebook/dp/B004GEBQS6/ref=kinw_dp_ke?ie=UTF8\u0026m=AG56TWVU5XWC2))\n * [README for core.logic](https://github.com/clojure/core.logic#readme)\n * Ambrose Bonnaire-Sergeant's [Logic Starter tutorial](https://github.com/frenchy64/Logic-Starter/wiki)\n \n#### Prolog ####\n\n * [Learn Prolog Now](http://www.learnprolognow.org/)\n * [Artifical Intelligence through Prolog](http://faculty.nps.edu/ncrowe/book/book.html)\n * [The Art of Prolog, 2nd Edition](http://www.amazon.com/The-Art-Prolog-Second-Edition/dp/0262193388)\n * [Prolog Programming for Artificial Intelligence](http://www.amazon.com/Programming-Artificial-Intelligence-International-Computer/dp/0321417461)\n\n## License ##\n\nCopyright (C) 2011 Daniel L. Gregoire (semperos)\n\nDistributed under the [Eclipse Public License](http://opensource.org/licenses/eclipse-1.0.php), the same as Clojure.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemperos%2Fwebdriver-logic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsemperos%2Fwebdriver-logic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemperos%2Fwebdriver-logic/lists"}