{"id":20340344,"url":"https://github.com/toolisticon/pogen4selenium","last_synced_at":"2026-01-11T17:05:24.605Z","repository":{"id":258523732,"uuid":"864206862","full_name":"toolisticon/pogen4selenium","owner":"toolisticon","description":"A page object generator for selenium","archived":false,"fork":false,"pushed_at":"2025-12-17T13:58:47.000Z","size":204,"stargazers_count":1,"open_issues_count":12,"forks_count":0,"subscribers_count":6,"default_branch":"develop","last_synced_at":"2025-12-21T02:20:33.322Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","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/toolisticon.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-09-27T17:37:15.000Z","updated_at":"2025-12-17T13:58:51.000Z","dependencies_parsed_at":"2025-12-17T22:01:33.394Z","dependency_job_id":null,"html_url":"https://github.com/toolisticon/pogen4selenium","commit_stats":null,"previous_names":["toolisticon/pogen4selenium"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/toolisticon/pogen4selenium","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolisticon%2Fpogen4selenium","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolisticon%2Fpogen4selenium/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolisticon%2Fpogen4selenium/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolisticon%2Fpogen4selenium/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/toolisticon","download_url":"https://codeload.github.com/toolisticon/pogen4selenium/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolisticon%2Fpogen4selenium/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28314264,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T14:58:17.114Z","status":"ssl_error","status_checked_at":"2026-01-11T14:55:53.580Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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-14T21:21:13.490Z","updated_at":"2026-01-11T17:05:24.599Z","avatar_url":"https://github.com/toolisticon.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pogen4selenium - a page object generator 4 selenium\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.pogen4selenium/pogen4selenium/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.pogen4selenium/pogen4selenium)\n[![Maven Central](https://maven-badges.sml.io/sonatype-central/io.toolisticon.pogen4selenium/pogen4selenium/badge.svg)](https://maven-badges.sml.io/sonatype-central/io.toolisticon.pogen4selenium/pogen4selenium)\n\n\n# Why you should use this project?\n\nPage Objects are a good way to produce a maintainable and reusable codebase to implement automated ui tests with selenium.\nNevertheless those page objects can contain a lot of (selenium related) boilerplate code that is mostly reused via copy and paste. (like for example having fluent waits or Actions)\nThis project provides processors to generate page object implementations by processing annotations placed on interfaces. \nBy doing that it drastically increases readability of page objects and reduces time for development.\n\n\n\n# Features\n\n- generates page object class implementations based on annotated interfaces\n- page objects support mixins - interfaces can extend multiple other interfaces and will inherit all of their elements and methods\n- generates extraction data class implementations base on annotated interfaces.\n- actions like clicking of elements or writing to input fields can be configured via annotations\n- the api enforces creation of a fluent api that improves writing of tests. Doing assertions or executing of custom code is also embedded into this fluent api\n\n# Restrictions\n\nThe project is still in development, so it currently just supports a few Actions like clicking or writing to input fields. Nevertheless, it's quite simple to define custom Action annotations and their implementations. \n\nPlease create an issue, if you need specific action to be implemented. Usually it will be included shortly after ;)\n\n# How does it work?\n\n## Project Setup\n\nThe api lib must be bound as a dependency - for example in maven:\n```xml\n\u003cdependencies\u003e\n\n\t\u003cdependency\u003e\n\t    \u003cgroupId\u003eio.toolisticon.pogen4selenium\u003c/groupId\u003e\n\t    \u003cartifactId\u003epogen4selenium-api\u003c/artifactId\u003e\n\t    \u003cversion\u003e0.12.0\u003c/version\u003e\n\t    \u003cscope\u003eprovided\u003c/scope\u003e\n\t\u003c/dependency\u003e\n \n\u003c/dependencies\u003e\n```\n\nAdditionally, you need to declare the annotation processor path in your compiler plugin:\n\n```xml\n\u003cplugin\u003e\n    \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n\n    \u003cconfiguration\u003e\n        \n        \u003cannotationProcessorPaths\u003e\n            \u003cpath\u003e\n                \u003cgroupId\u003eio.toolisticon.pogen4selenium\u003c/groupId\u003e\n                \u003cartifactId\u003epogen4selenium-processor\u003c/artifactId\u003e\n                \u003cversion\u003e0.12.0\u003c/version\u003e\n            \u003c/path\u003e\n        \u003c/annotationProcessorPaths\u003e\n        \n    \u003c/configuration\u003e\n\n\u003c/plugin\u003e\n```\n\n## Documentation\n\n### Defining Page Objects\nThe page object interfaces must be annotated with the *PageObject* annotation and must extend the *PageObjectParent* interface.\n\nThere are basically two ways to reference elements.\nThe first is to use element fields in the generated classes that will internally be initialized via Seleniums PageFactory.\n\nTo achieve that it's necessary to add String constants annotated with the *PageObjectElement* annotation for all web elements which are related with the page object. The constant values must be unique inside the interface and will be used in the generated class as variable names for the corresponding elements. The constant names must have \"_ID\" as suffix.\n\nThe other way is to use By locators on the fly by configuring them directly in the action annotations.\n\nThose action annotations must be placed on PageObject interface methods or method parameters. Those methods must return another page object interface.\n\nIt's also possible to add methods to extract data. Those methods must be annotated with the *ExtractData* annotation and must return an instance or List of a data extraction type (see next section)\n\nYou are able to use default methods to use custom code.\n\nOur [example website](pogen4selenium-example/src/test/resources/TestPage.html) is just a simple html file. This example demonstrates how to setup the page object:\n\n\n```java\n@PageObject\npublic interface TestPagePageObject extends PageObjectParent\u003cTestPagePageObject\u003e{\n\n\tstatic final String DATA_EXTRACTION_FROM_TABLE_XPATH = \"//table//tr[contains(@class,'data')]\";\n\t\n\tTestPagePageObject writeToInputField(@ActionWrite(by=_By.ID, value=\"input_field\") String value);\n\t\n\t@ExtractDataValue(by=_By.ID, value = \"input_field\", kind=Kind.ATTRIBUTE, name=\"value\")\n\tString readInputFieldValue();\n\t\n\t@ActionMoveToAndClick(by=_By.XPATH, value = \"//fieldset[@name='counter']/input[@type='button']\")\n\t@Pause(value = 500L)\n\tTestPagePageObject clickCounterIncrementButton();\t\n\t\n\t@ExtractData(by = io.toolisticon.pogen4selenium.api._By.XPATH, value = DATA_EXTRACTION_FROM_TABLE_XPATH)\n\tList\u003cTestPageTableEntry\u003e getTableEntries();\n\t\n\t@ExtractData(by = io.toolisticon.pogen4selenium.api._By.XPATH, value = DATA_EXTRACTION_FROM_TABLE_XPATH)\n\tTestPageTableEntry getFirstTableEntry();\n\t\n\t@ExtractDataValue(by = _By.XPATH, value=\"//fieldset[@name='counter']/span[@id='counter']\")\n\tString getCounter();\n\t\n\t// you can always provide your own methods and logic\n\tdefault String providedGetCounter() {\n\t\treturn getDriver().findElement(org.openqa.selenium.By.xpath(\"//fieldset[@name='counter']/span[@id='counter']\")).getText();\n\t}\n\t\n\t// Custom entry point for starting your tests\n\tpublic static TestPagePageObject init(WebDriver driver) {\n\t\tdriver.get(\"http://localhost:9090/start\");\n\t\treturn new TestPagePageObjectImpl(driver);\n\t}\n\n}\n```\n\n\n### Defining Interfaces For Data Extraction\n\nInterfaces for data extraction must be annotated with the *DataToExtract* annotation.\nThe methods for reading extracted data values must be  annotated with the *DataToExtractValue* annotation. Like with selenium it's possible to use different mechanics to locate the web elements to read the values from. Please make sure to use to prefix xpath expressions with \"./\" to locate elements relative to the Root element for extraction defined in the page objects extraction method.\n\nExtracting table data for our small [example](pogen4selenium-example/src/test/resources/TestPage.html):\n\n```java\n@DataObject\npublic interface TestPageTableEntry {\n\n\t// will use XPATH locator by default if by attribute isn't set explicitly\n\t@ExtractDataValue(by = _By.XPATH, value = \"./td[1]\")\n\tString name();\n\t\n\t@ExtractDataValue( value = \"./td[2]\")\n\tString age();\n\t\n\t@ExtractDataValue( value = \"./td[3]/a\",  kind = Kind.ATTRIBUTE, name = \"href\")\n\tString link();\n\t\n\t@ExtractDataValue(, value = \"./td[3]/a\", kind = Kind.TEXT)\n\tString linkText();\n\t\n}\n```\n\n### Using Layers Of Page Objects\nPage Objects can also be used to create abstraction layers.\n\nThink about the use case that you have to fill out a form where you have to fill out multiple fields belonging to an address.\nIn this case it's likely that you want to create a Page Object that does some aggregations .\n\nFirst you would create the Page Object that defines all actions \n\n```java\n\n@PageObject\npublic class PersonalDataFormPageObject extends PageObjectParent\u003cTestPagePageObject\u003e{\n\n\tPersonalDataFormPageObject writeName(@ActionWrite(by=_By.ID, value=\"name\")String name);\n\n\tPersonalDataFormPageObject writeStreet(@ActionWrite(by=_By.ID, value=\"street\")String street);\n\n\tPersonalDataFormPageObject writeHouseNumber(@ActionWrite(by=_By.ID, value=\"housenumber\") String houseNumber);\n\t\n\tPersonalDataFormPageObject writeZipCode(@ActionWrite(by=_By.ID, value=\"zipCode\")String zipCode);\n\n\tPersonalDataFormPageObject writeZipCode(@ActionWrite(by=_By.ID, value=\"city\")String city);\n\n}\n\n\n```\n\nThen you could create another page object later used in tests, that does some aggregation:\n\n```java\n\n@PageObject\npublic class AggregatedPersonalDataFormPageObject extends PageObjectParent\u003cAggregatedPersonalDataFormPageObject\u003e{\n\n\tdefault AggregatedPersonalDataFormPageObject writeAddress(Address address) {\n\t\tthis.changePageObjectType(PersonalDataFormPageObject.class)\n\t\t\t.writeName(address.getName())\n\t\t\t.writeStreet(address.getStreet())\n\t\t\t.writeHouseNumber(address.getHouseNumber())\n\t\t\t.writeZipCode(address.getZipCode())\n\t\t\t.writeCity(address.getCity());\n\t\t\t\n\t\treturn this;\n\t}\n\n}\n\n```\n\nIt's a good practice to use such aggregation page objects to group actions that belong together to make the automation code base more readable.\n\n### Writing tests\nWriting tests is easy. The fluent api provides a *doAssertions* method that allows you to inline custom assertions done with your favorite unit testing tool.\n\n```java\npublic class TestPageTest {\n\n\n\tprivate WebDriver webDriver;\n\tprivate JettyServer jettyServer;\n\t\n\t@Before\n\tpublic void init() throws Exception{\n\t\t\n\t\tjettyServer = new JettyServer();\n\t\tjettyServer.start();\n\t\t\n\t\twebDriver = new EdgeDriver();\n\t\twebDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));\n\t}\n\t\n\t@After\n\tpublic void cleanup() throws Exception{\n\t\twebDriver.quit();\n\t\tjettyServer.stop();\n\t}\n\t\n\t@Test\n\tpublic void extractDatasetsTest() {\n\t\tTestPagePageObject.init(webDriver)\n\t\t.doAssertions(e -\u003e {\n\t\t\t\t\n\t\t\t// Do assertions here\n\t\t\tList\u003cTestPageTableEntry\u003e results = e.getTableEntries();\n\t\t\t\n\t\t\tMatcherAssert.assertThat(results, Matchers.hasSize(2));\n\t\t\t\n\t\t\tMatcherAssert.assertThat(results.getFirst().name(), Matchers.is(\"Max\"));\n\t\t\tMatcherAssert.assertThat(results.getFirst().age(), Matchers.is(\"9\"));\n\t\t\tMatcherAssert.assertThat(results.getFirst().link(), Matchers.is(\"https://de.wikipedia.org/wiki/Max_und_Moritz\"));\n\t\t\tMatcherAssert.assertThat(results.getFirst().linkText(), Matchers.is(\"Max und Moritz Wikipedia\"));\n\t\t\t\n\t\t\t\n\t\t\tMatcherAssert.assertThat(results.get(1).name(), Matchers.is(\"Moritz\"));\n\t\t\tMatcherAssert.assertThat(results.get(1).age(), Matchers.is(\"10\"));\n\t\t\tMatcherAssert.assertThat(results.get(1).link(), Matchers.is(\"https://de.wikipedia.org/wiki/Wilhelm_Busch\"));\n\t\t\tMatcherAssert.assertThat(results.get(1).linkText(), Matchers.is(\"Wilhelm Busch Wikipedia\"));\n\t\t\t\t\n\n\t\t\t});\n\t}\n\t\n\t@Test\n\tpublic void extractFirstDatasetTest() {\n\t\tTestPagePageObject.init(webDriver)\n\t\t.doAssertions(e -\u003e {\n\t\t\t\t\n\t\t\t// Do assertions here\n\t\t\tTestPageTableEntry result = e.getFirstTableEntry();\n\t\t\t\n\t\t\t\n\t\t\tMatcherAssert.assertThat(result.name(), Matchers.is(\"Max\"));\n\t\t\tMatcherAssert.assertThat(result.age(), Matchers.is(\"9\"));\n\t\t\tMatcherAssert.assertThat(result.link(), Matchers.is(\"https://de.wikipedia.org/wiki/Max_und_Moritz\"));\n\t\t\tMatcherAssert.assertThat(result.linkText(), Matchers.is(\"Max und Moritz Wikipedia\"));\n\t\t\t\n\t\n\n\t\t});\n\t}\n\t\n\t@Test\n\tpublic void incrementCounterTest() {\n\t\tTestPagePageObject.init(webDriver)\n\t\t\t.doAssertions(e -\u003e {\n\t\t\t\t\tMatcherAssert.assertThat(e.getCounter(), Matchers.is(\"1\"));\n\t\t\t\t})\n\t\t\t.clickCounterIncrementButton()\n\t\t\t.doAssertions(e -\u003e {\n\t\t\t\tMatcherAssert.assertThat(e.getCounter(), Matchers.is(\"2\"));\n\t\t\t})\n\t\t\t.clickCounterIncrementButton()\n\t\t\t.clickCounterIncrementButton()\n\t\t\t.doAssertions(e -\u003e {\n\t\t\t\tMatcherAssert.assertThat(e.getCounter(), Matchers.is(\"4\"));\n\t\t\t});\n\t}\n\t\n\t\n}\n\n```\n\n#### Methods provided by fluent api\n\nThere are some default methods provided by the fluent api:\n\n#### getDriver ()\nGets the WebDriver in use. This is very useful in doAssertions or execute methods to manually execute selenium related code. \n\n##### verify ()\nBy using the verify methods it's possible to do check state of elements, i.e. if url matches a regular expression or if elements are present or clickable. Expected state is configured in PageObjectElement annotation. If not set explicitly all elements are expected to be present by default.\n\n##### doAssertions (AssertionInterface\u003cPAGEOBJECT\u003e function)\nIt's possible to inline assertions done via your favorite testing tools. \nBy providing this method it's not necessary to hassle with local variables anymore.\n\n##### execute (ExecuteBlock\u003cPAGEOBJECT, OPO\u003e function)\nThe execute method allows you to execute code dynamically without loosing the context of the fluent api.\nThis can be quite useful for reading data from the web page and doing things based on the extracted data.\n\n##### changePageObjectType (Class\u003cAPO extends PageObjectParent\u003cAPO\u003e\u003e targetPageObjectType)\nAllows changing the page objects type if expected behavior leaves the 'happy path' - for example if you expect to encounter a failing form validation or similar things.\n\n##### pause (Duration duration)\nIt's possible to enforce an explicit pause time by using this method\n\n##### waitForPageToContainText (String text)  or waitForPageToContainText (String text, Duration timeout)\nWait as long as a specific text is present on page. The text can either be a static text or a localized text if it matches the following pattern '${key}'. Localization will internally provided by a thread local resource bundle.\nThe resource bundle can be configured via the LocalizationUtilities class.\nThis method is a great help to wait until the expected page is being loaded and is displayed.\n\n##### setLocale (Locale locale)\nIt's possible to change the locale via this method\n \n## Extensibility\nNew action annotations can added by providing an action annotation annotated itself with the _Action_ meta annotation. Annotations must either be applicable to methods or method parameters.\n\nPlease see the _ActionWrite_ Annotation as an example\n```java\n@Target(ElementType.PARAMETER)\n@Retention(RetentionPolicy.RUNTIME)\n@Action(ActionWriteImpl.class)\npublic @interface ActionWrite {\n\t\n\t/**\n\t * The locator type to use. Can be ELEMENT for using a generated element or any kind of locator provided by Selenium.\n\t * @return the locator to use.\n\t */\n\t@LocatorBy\n\t_By by() default _By.ELEMENT;\n\t\n\t/** The locator string to use. */\n\t@LocatorValue\n\tString value();\n\t\n\t/**\n\t * The locator strategy to use, will just be taken into account if by attribute is not set to ELEMENT.\n\t * @return the Locator strategy, defaults to DefaultLocatorStrategy\n\t */\n\t@LocatorSideCondition\n\tClass\u003c? extends LocatorCondition\u003e locatorSideCondition() default DefaultSideCondition.class;\n\t\n}\n```\nThe _Action_ annotation is used to bind an implementation for the action annotation. It's also possible to map annotation attributes to constructor methods.\n\nThe action implementation must extend the BaseAction class:\n\n```java\npublic class ActionWriteImpl extends BaseAction {\n\t\n\tprivate final String toSet;\n\t\n\tpublic ActionWriteImpl(WebDriver driver, SearchContext searchContext, LocatorCondition locatorCondition, String toSet) {\n\t\tsuper(driver, searchContext, locatorCondition);\n\t\t\n\t\tthis.toSet = toSet;\n\t}\n\n\t@Override\n\tpublic boolean checkCondition(WebDriver driver, WebElement element) {\n\t\treturn element.isDisplayed() \u0026\u0026 element.isEnabled();\n\t}\n\n\t@Override\n\tpublic Collection\u003cClass\u003c? extends Throwable\u003e\u003e exceptionsToIgnore() {\n\t\treturn Arrays.asList(NoSuchElementException.class);\n\t}\n\n\t@Override\n\tprotected void applyAction(WebElement webElement) {\n\t\t\n\t\twebElement.click();\n\t\twebElement.sendKeys(Keys.CONTROL + \"a\");\n\t\twebElement.sendKeys(Keys.DELETE);\n\t\twebElement.sendKeys(toSet);\n\t\t\n\t}\n\n}\n```\n\nIt must have a constructor with at least the following parameters:\n\n```\nWebDriver driver, SearchContext searchContext, LocatorCondition locatorCondition\n```\n\nIf the action can be applied to a method parameter, it must have an additional parameter:\n\n```\nWebDriver driver, SearchContext searchContext, LocatorCondition locatorCondition, String toSet\n```\n\nAdditional annotation attributes can be mapped to constructor parameters call by using the Action annotations _attributeNameToConstructorMapping_ attribute.\n\n```java\n@Target(ElementType.PARAMETER)\n@Retention(RetentionPolicy.RUNTIME)\n@Action(value = ActionDragFromToImpl.class, attributeNameToConstructorMapping = {\"fromBy\", \"fromValue\"})\npublic @interface ActionDragFromTo {\n\n\t/**\n\t * The locator type to use. Can be ELEMENT for using a generated element or any kind of locator provided by selenium.\n\t * @return the locator to use.\n\t */\t\n\t_By fromBy() default _By.XPATH;\n\t\n\t/** The locator string to use. */\t\n\tString fromValue() default \"${}\";\n\t\n\t@LocatorBy\n\t_By toBy() default _By.XPATH;\n\t\n\t/** The locator string to use. */\n\t@LocatorValue\n\tString toValue();\n\t\n\t/**\n\t * The locator strategy to use, will just be taken into account if by attribute is not set to ELEMENT.\n\t * @return the Locator strategy, defaults to DefaultLocatorStrategy\n\t */\n\t@LocatorSideCondition\n\tClass\u003c? extends LocatorCondition\u003e locatorSideCondition() default DefaultSideCondition.class;\n\t\n}\n```\n\n\n## Best practices\n\nThere are a few things you should consider as best practices\n\n- Use a multi layer approach. The lowest layer should provide all actions that can be done on page \n- Naming convention: Please use specific prefixes for you page object methods. This can be 'do' for all actions and 'get' for reading data.\n- Page objects should define just the happy path. Special cases like failing validations can be handled in the unit tests via the execute method(you can change the page object type via the changePageObjectType method in it).   \n\n## Example\n\nPlease check the our example submodule: [example](pogen4selenium-example/)\n\nIt contains a basic example page demonstrating the usage of all available actions and data extraction.\nA second example shows how to test the google search page.\n    \n# Contributing\n\nWe welcome any kind of suggestions and pull requests.\n\n## Building and developing the pogen4selenium annotation processor\n\nThe pogen4selenium is built using Maven.\nA simple import of the pom in your IDE should get you up and running. To build the pogen4selenium on the commandline, just run `mvn` or `mvn clean install`\n\n## Requirements\n\nThe likelihood of a pull request being used rises with the following properties:\n\n- You have used a feature branch.\n- You have included a test that demonstrates the functionality added or fixed.\n- You adhered to the [code conventions](http://www.oracle.com/technetwork/java/javase/documentation/codeconvtoc-136057.html).\n\n## Contributions\n\n\n# License\n\nThis project is released under the revised [MIT License](LICENSE).\n\nThis project includes and repackages the [Annotation-Processor-Toolkit](https://github.com/holisticon/annotation-processor-toolkit) released under the  [MIT License](/3rdPartyLicenses/annotation-processor-toolkit/LICENSE.txt).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoolisticon%2Fpogen4selenium","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoolisticon%2Fpogen4selenium","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoolisticon%2Fpogen4selenium/lists"}