diff --git a/.gitattributes b/.gitattributes index f91f646..b5d8f35 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,3 +10,5 @@ # Binary files should be left untouched *.jar binary +# Ensure all Java files use LF. +*.java eol=lf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d5cc794..97bb679 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,5 +3,5 @@ # See: https://help.github.com/articles/about-codeowners/ -# TODO: Add code owners # These owners will be the default owners for everything in the repo. +* @DharshiBalasubramaniyam @shafreenAnfar @daneshk @ThisaruGuruge diff --git a/.github/worflows/build-with-bal-test-graalvm.yml b/.github/worflows/build-with-bal-test-graalvm.yml deleted file mode 100644 index 6314373..0000000 --- a/.github/worflows/build-with-bal-test-graalvm.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: GraalVM Check - -on: - schedule: - - cron: "30 18 * * *" - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: true - -jobs: - call_stdlib_workflow: - name: Run StdLib Workflow - if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} - uses: ballerina-platform/ballerina-library/.github/workflows/build-with-bal-test-graalvm-connector-template.yml@main - secrets: inherit - \ No newline at end of file diff --git a/.github/worflows/ci.yml b/.github/worflows/ci.yml deleted file mode 100644 index b620f87..0000000 --- a/.github/worflows/ci.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Build - -on: - push: - branches: - - main - - 2201.[0-9]+.x - repository_dispatch: - types: check_connector_for_breaking_changes - -jobs: - call_workflow: - name: Run Connector Build Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/build-connector-template.yml@main - secrets: inherit - with: - repo-name: module-selenium - \ No newline at end of file diff --git a/.github/worflows/daily-build.yml b/.github/worflows/daily-build.yml deleted file mode 100644 index ea618d7..0000000 --- a/.github/worflows/daily-build.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Daily build - -on: - schedule: - - cron: "30 2 * * *" - -jobs: - call_workflow: - name: Run Daily Build Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/daily-build-connector-template.yml@main - secrets: inherit - with: - repo-name: module-selenium - \ No newline at end of file diff --git a/.github/worflows/dev-stg-release.yml b/.github/worflows/dev-stg-release.yml deleted file mode 100644 index 5c06fb0..0000000 --- a/.github/worflows/dev-stg-release.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Publish to the Ballerina Dev\Stage Central - -on: - workflow_dispatch: - inputs: - environment: - type: choice - description: Select Environment - required: true - options: - - DEV CENTRAL - - STAGE CENTRAL - -jobs: - call_workflow: - name: Run Dev\Stage Central Publish Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/dev-stage-central-publish-connector-template.yml@main - secrets: inherit - with: - environment: ${{ github.event.inputs.environment }} - \ No newline at end of file diff --git a/.github/worflows/pull-request.yml b/.github/worflows/pull-request.yml deleted file mode 100644 index dc3176f..0000000 --- a/.github/worflows/pull-request.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: PR Build - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: true - -on: pull_request - -jobs: - call_workflow: - name: Run PR Build Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/pr-build-connector-template.yml@main - secrets: inherit - with: - additional-test-flags: ${{ github.event.pull_request.head.repo.full_name != github.repository && '-x test' || ''}} - \ No newline at end of file diff --git a/.github/worflows/release.yml b/.github/worflows/release.yml deleted file mode 100644 index 70bc52d..0000000 --- a/.github/worflows/release.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Publish Release - -on: - workflow_dispatch: - repository_dispatch: - types: [ stdlib-release-pipeline ] - -jobs: - call_workflow: - name: Run Release Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/release-package-connector-template.yml@main - secrets: inherit - with: - package-name: selenium - package-org: xlibb - \ No newline at end of file diff --git a/.github/worflows/trivy-scan.yml b/.github/worflows/trivy-scan.yml deleted file mode 100644 index 87f91f2..0000000 --- a/.github/worflows/trivy-scan.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Trivy - -on: - workflow_dispatch: - schedule: - - cron: "30 20 * * *" - -jobs: - call_workflow: - name: Run Trivy Scan Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/trivy-scan-template.yml@main - secrets: inherit - \ No newline at end of file diff --git a/.github/workflows/build-timestamped-master.yml b/.github/workflows/build-timestamped-master.yml new file mode 100644 index 0000000..e5348c9 --- /dev/null +++ b/.github/workflows/build-timestamped-master.yml @@ -0,0 +1,56 @@ +name: Build + +on: + push: + branches: + - main + paths-ignore: + - '*.md' + - 'docs/**' + - 'load-tests/**' + + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + if: github.repository_owner == 'xlibb' + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + + - name: Change to Timestamped Version + run: | + startTime=$(TZ="Asia/Kolkata" date +'%Y%m%d-%H%M00') + latestCommit=$(git log -n 1 --pretty=format:"%h") + VERSION=$((grep -w 'version' | cut -d= -f2) < gradle.properties | rev | cut --complement -d- -f1 | rev) + updatedVersion=$VERSION-$startTime-$latestCommit + echo $updatedVersion + sed -i "s/version=\(.*\)/version=$updatedVersion/g" gradle.properties + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + env: + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + publishUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + publishPAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + run: ./gradlew publish --no-daemon --scan + + - name: Generate Codecov Report + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: ballerina-runtime + path: target/ballerina-runtime/ diff --git a/.github/workflows/central-publish.yml b/.github/workflows/central-publish.yml new file mode 100644 index 0000000..8f75cf0 --- /dev/null +++ b/.github/workflows/central-publish.yml @@ -0,0 +1,81 @@ +name: Publish to the Ballerina central + +on: + workflow_dispatch: + inputs: + environment: + type: choice + description: Select Environment + required: true + options: + - DEV CENTRAL + - STAGE CENTRAL + +jobs: + publish-release: + runs-on: ubuntu-latest + if: github.repository_owner == 'xlibb' + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + env: + packageUser: ${{ github.actor }} + packagePAT: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew build -x check -x test + + - name: Create lib directory if not exists + run: mkdir -p ballerina/lib + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.23.0 + with: + scan-type: "rootfs" + scan-ref: '/github/workspace/ballerina/lib' + format: "table" + timeout: "10m0s" + exit-code: "1" + scanners: "vuln" + + - name: Publish artifact + env: + BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_ACCESS_TOKEN }} + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + run: ./gradlew clean build -PpublishToCentral=true + + - name: Ballerina Central Dev Push + if: ${{ inputs.environment == 'DEV CENTRAL' }} + env: + BALLERINA_DEV_CENTRAL: true + BALLERINA_STAGE_CENTRAL: false + BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_DEV_ACCESS_TOKEN }} + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + run: | + sed -i 's/version=\(.*\)-SNAPSHOT/version=\1/g' gradle.properties + ./gradlew clean build -PpublishToCentral=true ${{ inputs.additional-publish-flags }} + - name: Ballerina Central Stage Push + if: ${{ inputs.environment == 'STAGE CENTRAL' }} + env: + BALLERINA_DEV_CENTRAL: false + BALLERINA_STAGE_CENTRAL: true + BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_STAGE_ACCESS_TOKEN }} + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + run: | + sed -i 's/version=\(.*\)-SNAPSHOT/version=\1/g' gradle.properties + ./gradlew clean build -PpublishToCentral=true ${{ inputs.additional-publish-flags }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..4b8cd07 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,86 @@ +name: Publish Release + +on: + workflow_dispatch: + repository_dispatch: + types: [ stdlib-release-pipeline ] + +jobs: + publish-release: + name: Release Package + runs-on: ubuntu-latest + if: github.repository_owner == 'xlibb' + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + env: + packageUser: ${{ github.actor }} + packagePAT: ${{ secrets.GITHUB_TOKEN }} + run: | + git config --global user.name ${{ secrets.BALLERINA_BOT_USERNAME }} + git config --global user.email ${{ secrets.BALLERINA_BOT_EMAIL }} + ./gradlew build -x check -x test -x :selenium-examples:build + + - name: Create lib directory if not exists + run: mkdir -p ballerina/lib + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.23.0 + with: + scan-type: "rootfs" + scan-ref: '/github/workspace/ballerina/lib' + format: "table" + timeout: "10m0s" + exit-code: "1" + scanners: "vuln" + + - name: Get Release Version + run: echo "VERSION=$((grep -w 'version' | cut -d= -f2) < gradle.properties | rev | cut --complement -d- -f1 | rev)" >> $GITHUB_ENV + + - name: Pre release dependency version update + env: + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + run: | + echo "Version: ${VERSION}" + git checkout -b release-${VERSION} + sed -i 's/ballerinaLangVersion=\(.*\)-SNAPSHOT/ballerinaLangVersion=\1/g' gradle.properties + sed -i 's/ballerinaLangVersion=\(.*\)-[0-9]\{8\}-[0-9]\{6\}-.*$/ballerinaLangVersion=\1/g' gradle.properties + sed -i 's/stdlib\(.*\)=\(.*\)-SNAPSHOT/stdlib\1=\2/g' gradle.properties + sed -i 's/stdlib\(.*\)=\(.*\)-[0-9]\{8\}-[0-9]\{6\}-.*$/stdlib\1=\2/g' gradle.properties + sed -i 's/observe\(.*\)=\(.*\)-SNAPSHOT/observe\1=\2/g' gradle.properties + sed -i 's/observe\(.*\)=\(.*\)-[0-9]\{8\}-[0-9]\{6\}-.*$/observe\1=\2/g' gradle.properties + git add gradle.properties + git commit -m "Move dependencies to stable version" || echo "No changes to commit" + + - name: Publish Package + env: + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_ACCESS_TOKEN }} + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + publishUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + publishPAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + run: | + ./gradlew clean release -Prelease.useAutomaticVersion=true + ./gradlew -Pversion=${VERSION} publish -x test -PpublishToCentral=true + + - name: GitHub Release and Release Sync PR + env: + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + run: | + gh release create v$VERSION --title "module-selenium-v$VERSION" + gh pr create --title "[Automated] Sync main after $VERSION release" --body "Sync main after $VERSION release" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..9479511 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,51 @@ +name: Pull Request + +on: pull_request + +jobs: + ubuntu-build: + name: Build on Ubuntu + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 17.0.7 + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build the Package + env: + packageUser: ${{ github.actor }} + packagePAT: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew build --no-daemon --scan + + - name: Generate Codecov Report + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + windows-build: + name: Build on Windows + runs-on: windows-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + + - name: Build the Project + env: + packageUser: ${{ github.actor }} + packagePAT: ${{ secrets.GITHUB_TOKEN }} + JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8 + run: ./gradlew.bat build --no-daemon --scan diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml new file mode 100644 index 0000000..ce5625d --- /dev/null +++ b/.github/workflows/trivy-scan.yml @@ -0,0 +1,41 @@ +name: Trivy + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + +jobs: + ubuntu-build: + name: Build on Ubuntu + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + env: + packageUser: ${{ github.actor }} + packagePAT: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew build -x check -x test + + - name: Create lib directory if not exists + run: mkdir -p ballerina/lib + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.23.0 + with: + scan-type: 'rootfs' + scan-ref: '/github/workspace/ballerina/lib' + format: 'table' + timeout: '10m0s' + exit-code: '1' diff --git a/.gitignore b/.gitignore index bda5bd7..6093771 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ *.tar.gz *.rar *.deb +bin # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* diff --git a/README.md b/README.md index e95ea24..f8cdf9c 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,355 @@ This repository contains the source code of the Ballerina Selenium library packa ## Overview -[//]: # (TODO: Add overview mentioning the purpose of the module, supported REST API versions, and other high-level details.) +This module automates web applications across various browsers. Selenium interacts with web browsers directly, simulating user actions such as clicks, text input, page navigation, and more. -## Setup guide +## Quickstart -[//]: # (TODO: Add detailed steps to obtain credentials and configure the module.) +### Import the module -## Quickstart +```ballerina +import xlibb/selenium; +``` + +### Create a new web driver instance + +The core component of Selenium is the `WebDriver`. It is an interface for controlling a web browser instance and communicates with the browser through its native API. + +The constructor of the `WebDriver` takes `BrowserOptions` as an argument, which contains the following options: + +1. `browserName` - A enum value specifies the type of browser to open. Acceptable values are `selenium:CHROME` or `selenium:FIREFOX`. The default is `selenium:CHROME`. +2. `url` - The URL of the web application to open in the browser. +3. `headlessMode` - A boolean value indicating whether to run the browser in headless mode (without a GUI). The default is `false`. +4. `incognitoMode` - A boolean value indicating whether to run the browser in incognito mode. The default is `false`. +5. `additionalArguments` - A list of additional command-line arguments to pass to the browser during initialization. + +```ballerina +// Opens a new Chrome browser instance and navigates to the specified URL. +selenium:WebDriver driver = new ({ + url: "https://central.ballerina.io/" +}); +``` + +### Locating elements + +`WebElement` in `selenium` represents an element in the DOM, which allows interaction (e.g., click, send keys). + +A locator is a way to identify `WebElement` on a page. Selenium provides support for several location strategies(locators) in WebDriver: + +- **class name**: Locates elements by the value of their `class` attribute. +- **ID**: Locates elements by the value of their `id` attribute. +- **CSS selector**: Locates elements using CSS selectors, which are patterns used to select elements based on their attributes. +- **name**: Locates elements by the value of their `name` attribute. +- **tag name**: Locates elements by their tag name, such as `input`, `div`, etc. +- **XPath**: Locates elements using XPath expressions, which are used to navigate through elements and attributes in an XML document. +- **link text**: Locates anchor elements by the exact text they display. +- **partial link text**: Locates anchor elements by a partial match of the text they display. + +```ballerina +import xlibb/selenium; +import ballerina/io; + +public function main() { + // By ID + selenium:WebElement form = check driver.findById("form"); + + // By Name + selenium:WebElement searchboxByName = check driver.findByName("q"); + + // By CSS Selector + selenium:WebElement searchboxByCss = check driver.findByCssSelector("#form .search-box"); + + // By XPath + selenium:WebElement searchboxByXpath = check driver.findByXpath("//input[@id='search']"); + + // By Class Name + selenium:WebElement searchboxByClassName = check driver.findByClassName("search-box"); + + // By Tag Name + selenium:WebElement searchboxByTagName = check driver.findByTagName("input"); + + // Locating a WebElement inside another WebElement + selenium:WebElement nestedElement = check form.findByName("q"); + + // Locating all elements with same locator + selenium:WebElement[] inputElements = check driver.findAllByTagName("input"); +} + +``` + +### Fetching data over any `WebElement` + +To fetch data from a `WebElement` in Selenium, you can interact with it using various methods depending on the type of data you need. + +1. Text of an element: + +The `getText()` method in Selenium is used to retrieve the visible (i.e., not hidden by CSS) inner text of a `WebElement`. This method is commonly used to extract text from elements such as paragraphs, headings, labels, or any other HTML elements that contain text. + +```ballerina +selenium:WebElement label = check driver.findByClassName("input-label"); +string labelText = check label.getText(); +io:println(labelText); +``` + +2. Attribute value + +The `getDomAttribute` method in Selenium is used to retrieve the value of a specified attribute from a `WebElement`. This method is particularly useful when you need to access the underlying attributes of an element in the DOM (Document Object Model) that are not directly visible on the page. Commonly used to get attributes like value for input fields, href for links, src for images, etc. + +```ballerina +string value = (check driver.findElementById("elementId")).getDomAttribute("value"); // For input fields +string href = (check driver.findElementByTagName("a")).getDomAttribute("href"); // For links +``` + +3. Checking if an element is displayed/enabled/selected: + +```ballerina +selenium:WebElement element = check driver.findById("elementId"); +boolean isDisplayed = check element.isDisplayed(); +boolean isEnabled = check element.isEnabled(); +boolean isSelected = check element.isSelected(); +``` + +### Sending user inputs to `WebElement` + +To send user inputs to `WebElement`s, you can use the `sendKeys()` method, which simulates typing into text-based input fields (like textboxes, textareas, etc.). + +```ballerina +selenium:WebElement element = check driver.findByName("email"); +check element.sendKeys("exmaple@abc.com"); +``` + +### Performing Click event + +To perform a click event on a `WebElement`, you can use the `click()` method on the `WebElement` object. This is commonly used to simulate clicking on buttons, links, checkboxes, radio buttons, or any other clickable elements. + +```ballerina +selenium:WebElement element = check driver.findByClassName("submit-btn"); +check element.click(); +``` + +### Quit the driver + +```ballerina +check driver.quit(); +``` + +### Working with windows and tabs + +**Window handles** are unique identifiers for each browser window or tab. These can be used to programmatically manage multiple windows. + +```ballerina +// Initialize a new WebDriver instance +selenium:WebDriver driver = new ({ + url: "https://the-internet.herokuapp.com/windows" +}); + +// Print the title of the current window. +io:println(driver.getTitle()); // Prints "The Internet" + +// Store the current window handle. +string firstWindow = check driver.getCurrentWindowHandle(); + +// Click on the link that opens a new window/tab. +check (check driver.findByPartialLinkText("Click")).click(); + +// Get handles for all open windows. Currently, there are 2 windows open, +// so this array contains the handles of those 2 windows in the order they were opened. +string[] allWindows = check driver.getAllWindowHandles(); + +// Even though we opened a new window, the driver is still pointing to the first window. To interact with the new window, we need to switch to it using the window handle. +check driver.switchToWindowHandle(allWindows[1]); + +// Print the title of the currrent window. +io:println(driver.getTitle()); // Prints "New Window" + +// Close the second (current) window. +check driver.closeCurrentWindowHandle(); + +// Switch back to the first window. Now the driver is pointing to the first window. +check driver.switchToWindowHandle(firstWindow); + +// Print the title of the current window +io:println(driver.getTitle()); // Prints "The Internet" + +// Quit the driver and close all windows +check driver.quit(); +``` + +### Locating elements + +A locator is a way to identify elements on a page. Selenium provides support for several location strategies in WebDriver, such as class name, ID, CSS selector, name, tag name, and XPath. -[//]: # (TODO: Add a quickstart guide to demonstrate a basic functionality of the module, including sample code snippets.) +`WebElement` class: Represents an element in the DOM; allows interaction (e.g., click, send keys). + +```html +
+``` + +```ballerina +import xlibb/selenium; +import ballerina/io; + +public function main() { + + // By ID + selenium:WebElement form = check driver.findById("form"); + + // By Name + selenium:WebElement searchboxByName = check driver.findByName("q"); + + // By CSS Selector + selenium:WebElement searchboxByCss = check driver.findByCssSelector("#form .search-box"); + + // By XPath + selenium:WebElement searchboxByXpath = check driver.findByXpath("//input[@id='search']"); + + // By Class Name + selenium:WebElement searchboxByClassName = check driver.findByClassName("search-box"); + + // By Tag Name + selenium:WebElement searchboxByTagName = check driver.findByTagName("input"); + + // Locating a WebElement inside another WebElement + selenium:WebElement nestedElement = check form.findByName("q"); + + // Locating all elements with same locator + selenium:WebElement[] inputElements = check driver.findAllByTagName("input"); +} + +``` + +### Fetching data over any web element + +To fetch data from a web element in Selenium, you can interact with it using various methods depending on the type of data you need. + +1. Text of an element (e.g., a paragraph or label): + +```ballerina +selenium:WebElement label = check driver.findByClassName("input-label"); +string labelText = label.getText(); +io:println(labelText); +``` + +2. Attribute value (e.g., value of an input field or the href attribute of a link): + +```ballerina +string value = (check driver.findElementById("elementId")).getDomAttribute("value"); // For input fields +string href = (check driver.findElementByTagName("a")).getDomAttribute("href"); // For links +``` + + +3. Checking if an element is displayed/enabled/selected: + +```ballerina +selenium:WebElement element = check driver.findById("elementId"); +boolean isDisplayed = element.isDisplayed(); +boolean isEnabled = element.isEnabled(); +boolean isSelected = element.isSelected(); +``` + + +### Sending user inputs to web element + +To send user inputs to web elements, you can use the `sendKeys()` method, which simulates typing into text-based input fields (like textboxes, textareas, etc.). + +```ballerina +(check driver.findByClassName("search-box")).sendKeys("ballerina"); +``` + +### Performing Click event + +To perform a click event on a web element, you can use the `click()` method on the WebElement object. This is commonly used to simulate clicking on buttons, links, checkboxes, radio buttons, or any other clickable elements. + +```ballerina +(check driver.findByClassName("submit-btn")).click(); +``` + +### Closing the browser + +```ballerina +driver.quit(); +``` + +### Working with windows and tabs + +**Window handles** are unique identifiers for each browser window or tab. These can be used to programmatically manage multiple windows. + +```ballerina +// Initialize a new WebDriver instance +selenium:WebDriver driver = new ({ + url: "https://the-internet.herokuapp.com/windows" +}); + +// Print the title of the current window. +io:println(driver.getTitle()); // Prints "The Internet" + +// Store the current window handle. +string firstWindow = driver.getCurrentWindowHandle(); + +// Click on the link that opens a new window. +(check driver.findByPartialLinkText("Click")).click(); + +// Get handles for all open windows. +string[] allWindows = driver.getAllWindowHandles(); + +// Switch to the second window. Now the driver is pointing to the new window. +check driver.switchToWindowHandle(allWindows[1]); + +// Print the title of the currrent window. +io:println(driver.getTitle()); // Prints "New Window" + +// Close the second (current) window. +driver.closeCurrentWindowHandle(); + +// Switch back to the first window. Now the driver is pointing to the first window. +check driver.switchToWindowHandle(firstWindow); + +// Print the title of the current window +io:println(driver.getTitle()); // Prints "The Internet" + +// Quit the driver and close all windows +driver.quit(); + +``` + +### Selenium IDE for Finding Locators + +Selenium IDE is a handy tool for finding locators for web elements. Here's how to use it effectively: + +1. Install Selenium IDE: Download and install the Selenium IDE extension for your browser (Chrome or Firefox) [here](https://www.selenium.dev/selenium-ide/). +2. Launch Selenium IDE: Open the Selenium IDE extension from your browser's toolbar. + +3. Start a New Project: Click on Create a New Project and name your project. + + + +4. Start Recording: + - Click on the Record a New Test in a New Project option. + - Enter the URL of the web application you want to test. + - Selenium IDE will open the URL in a new tab and start recording your actions. + + + +5. Perform Actions on the Webpage: Interact with the web elements (e.g., click buttons, fill out forms) on the page. Selenium IDE will record these actions as steps in your test case. +6. View Recorded Steps: Once done, stop the recording. The recorded steps will appear in the Selenium IDE window. Each step will include information about the action and the locator used. +7. Inspect Locators: + - In the list of recorded steps, click on a step to view details. + - The Target field shows the locator for the web element (e.g., XPath, ID, Name, CSS selector, etc.). + - You can switch between different locator strategies by clicking the dropdown next to the locator. Selenium IDE will display alternatives if available. + + ## Examples The `Selenium` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/xlibb/module-selenium/tree/main/examples/), covering the following use cases: -[//]: # (TODO: Add examples) +1. [Web Scrapping.](https://github.com/xlibb/module-selenium/tree/main/examples/web_scrapping) + +2. [Automating the Filling of Student Application Forms for a Web Application.](https://github.com/xlibb/module-selenium/tree/main/examples/student_application_form_filling) ## Build from the source diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 6dd9a28..2be749b 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -5,8 +5,8 @@ name = "selenium" version = "0.1.0" license = ["Apache-2.0"] authors = ["Ballerina"] -keywords = [] # TODO: Add keywords -# icon = "icon.png" # TODO: Add icon +keywords = ["selenium", "web-automation"] +icon = "icon.png" repository = "https://github.com/xlibb/module-selenium" [build-options] @@ -14,3 +14,260 @@ observabilityIncluded = true [platform.java17] graalvmCompatible = true + +[[platform.java17.dependency]] +groupId = "io.xlibb.selenium" +artifactId = "selenium-native" +version = "0.1.0" +path = "../native/build/libs/selenium-native-0.1.0-SNAPSHOT.jar" + +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-java" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-api" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-api:4.27.0 +[[platform.java17.dependency]] +groupId = "org.jspecify" +artifactId = "jspecify" +version = "1.0.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-chrome-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-chrome-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.auto.service" +artifactId = "auto-service-annotations" +version = "1.1.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-chrome-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-chromium-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-chrome-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-json" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-chrome-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-manager" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-devtools-v129" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-devtools-v130" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-devtools-v131" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-devtools-v85" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-edge-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-firefox-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-firefox-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-http" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-firefox-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "dev.failsafe" +artifactId = "failsafe" +version = "3.3.2" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-ie-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-remote-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.guava" +artifactId = "guava" +version = "33.3.1-jre" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.guava" +artifactId = "failureaccess" +version = "1.0.2" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.guava" +artifactId = "listenablefuture" +version = "9999.0-empty-to-avoid-conflict-with-guava" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.code.findbugs" +artifactId = "jsr305" +version = "3.0.2" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.checkerframework" +artifactId = "checker-qual" +version = "3.43.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.errorprone" +artifactId = "error_prone_annotations" +version = "2.28.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.j2objc" +artifactId = "j2objc-annotations" +version = "3.0.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry.semconv" +artifactId = "opentelemetry-semconv" +version = "1.25.0-alpha" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-api" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-context" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-exporter-logging" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-common" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-extension-autoconfigure-spi" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-extension-autoconfigure" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-api-incubator" +version = "1.44.1-alpha" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-trace" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-metrics" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-logs" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "net.bytebuddy" +artifactId = "byte-buddy" +version = "1.15.10" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-os" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.apache.commons" +artifactId = "commons-exec" +version = "1.4.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-safari-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-support" +version = "4.27.0" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 20d94f8..ef62d24 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -17,44 +17,63 @@ modules = [ [[package]] org = "ballerina" -name = "jballerina.java.arrays" -version = "1.4.0" +name = "lang.__internal" +version = "0.0.0" +scope = "testOnly" dependencies = [ - {org = "ballerina", name = "jballerina.java"} + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} ] -modules = [ - {org = "ballerina", packageName = "jballerina.java.arrays", moduleName = "jballerina.java.arrays"} + +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} ] [[package]] org = "ballerina" -name = "lang.runtime" +name = "lang.error" version = "0.0.0" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -modules = [ - {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} -] [[package]] org = "ballerina" -name = "observe" -version = "1.3.0" +name = "lang.object" +version = "0.0.0" +scope = "testOnly" + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +modules = [ + {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} +] [[package]] -org = "ballerinai" -name = "observe" +org = "ballerina" +name = "test" version = "0.0.0" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "observe"} + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.error"} ] modules = [ - {org = "ballerinai", packageName = "observe", moduleName = "observe"} + {org = "ballerina", packageName = "test", moduleName = "test"} ] [[package]] @@ -63,35 +82,10 @@ name = "selenium" version = "0.1.0" dependencies = [ {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "jballerina.java.arrays"}, {org = "ballerina", name = "lang.runtime"}, - {org = "ballerinai", name = "observe"} + {org = "ballerina", name = "test"} ] modules = [ - {org = "xlibb", packageName = "selenium", moduleName = "selenium"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.java.io"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.java.lang"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.java.net"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.java.nio.file"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.java.util"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.java.util.function"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.java.util.logging"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.bidi"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.chrome"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.chromium"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.devtools"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.edge"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.federatedcredentialmanagement"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.firefox"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.html5"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.logging"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.mobile"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.print"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.remote"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.remote.http"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.remote.service"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.safari"}, - {org = "xlibb", packageName = "selenium", moduleName = "selenium.org.openqa.selenium.virtualauthenticator"} + {org = "xlibb", packageName = "selenium", moduleName = "selenium"} ] diff --git a/ballerina/Module.md b/ballerina/Module.md index 1f4ba23..0e995ed 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -1,17 +1,211 @@ ## Overview -[//]: # (TODO: Add overview mentioning the purpose of the module, supported REST API versions, and other high-level details.) +This module automates web applications across various browsers. Selenium interacts with web browsers directly, simulating user actions such as clicks, text input, page navigation, and more. -## Setup guide +## Quickstart -[//]: # (TODO: Add detailed steps to obtain credentials and configure the module.) +### Import the module -## Quickstart +```ballerina +import xlibb/selenium; +``` + +### Create a new web driver instance + +The core component of Selenium is the `WebDriver`. It is an interface for controlling a web browser instance and communicates with the browser through its native API. + +The constructor of the `WebDriver` takes `BrowserOptions` as an argument, which contains the following options: + +1. `browserName` - A enum value specifies the type of browser to open. Acceptable values are `selenium:CHROME` or `selenium:FIREFOX`. The default is `selenium:CHROME`. +2. `url` - The URL of the web application to open in the browser. +3. `headlessMode` - A boolean value indicating whether to run the browser in headless mode (without a GUI). The default is `false`. +4. `incognitoMode` - A boolean value indicating whether to run the browser in incognito mode. The default is `false`. +5. `additionalArguments` - A list of additional command-line arguments to pass to the browser during initialization. + +```ballerina +// Opens a new Chrome browser instance and navigates to the specified URL. +selenium:WebDriver driver = new ({ + url: "https://central.ballerina.io/", + additionalArguments: ["--start-maximized"] +}); +``` + +### Locating elements + +`WebElement` in `selenium` represents an element in the DOM, which allows interaction (e.g., click, send keys). + +A locator is a way to identify `WebElement` on a page. Selenium provides support for several location strategies(locators) in WebDriver: + +- **class name**: Locates elements by the value of their `class` attribute. +- **ID**: Locates elements by the value of their `id` attribute. +- **CSS selector**: Locates elements using CSS selectors, which are patterns used to select elements based on their attributes. +- **name**: Locates elements by the value of their `name` attribute. +- **tag name**: Locates elements by their tag name, such as `input`, `div`, etc. +- **XPath**: Locates elements using XPath expressions, which are used to navigate through elements and attributes in an XML document. +- **link text**: Locates anchor elements by the exact text they display. +- **partial link text**: Locates anchor elements by a partial match of the text they display. + +```ballerina +import xlibb/selenium; +import ballerina/io; + +public function main() { + // By ID + selenium:WebElement form = check driver.findById("form"); + + // By Name + selenium:WebElement searchboxByName = check driver.findByName("q"); + + // By CSS Selector + selenium:WebElement searchboxByCss = check driver.findByCssSelector("#form .search-box"); + + // By XPath + selenium:WebElement searchboxByXpath = check driver.findByXpath("//input[@id='search']"); + + // By Class Name + selenium:WebElement searchboxByClassName = check driver.findByClassName("search-box"); + + // By Tag Name + selenium:WebElement searchboxByTagName = check driver.findByTagName("input"); + + // Locating a WebElement inside another WebElement + selenium:WebElement nestedElement = check form.findByName("q"); + + // Locating all elements with same locator + selenium:WebElement[] inputElements = check driver.findAllByTagName("input"); +} + +``` + +### Fetching data over any `WebElement` + +To fetch data from a `WebElement` in Selenium, you can interact with it using various methods depending on the type of data you need. + +1. Text of an element: + +The `getText()` method in Selenium is used to retrieve the visible (i.e., not hidden by CSS) inner text of a `WebElement`. This method is commonly used to extract text from elements such as paragraphs, headings, labels, or any other HTML elements that contain text. + +```ballerina +selenium:WebElement label = check driver.findByClassName("input-label"); +string labelText = check label.getText(); +io:println(labelText); +``` + +2. Attribute value + +The `getDomAttribute` method in Selenium is used to retrieve the value of a specified attribute from a `WebElement`. This method is particularly useful when you need to access the underlying attributes of an element in the DOM (Document Object Model) that are not directly visible on the page. Commonly used to get attributes like value for input fields, href for links, src for images, etc. + +```ballerina +string value = (check driver.findElementById("elementId")).getDomAttribute("value"); // For input fields +string href = (check driver.findElementByTagName("a")).getDomAttribute("href"); // For links +``` + +3. Checking if an element is displayed/enabled/selected: + +```ballerina +selenium:WebElement element = check driver.findById("elementId"); +boolean isDisplayed = check element.isDisplayed(); +boolean isEnabled = check element.isEnabled(); +boolean isSelected = check element.isSelected(); +``` -[//]: # (TODO: Add a quickstart guide to demonstrate a basic functionality of the module, including sample code snippets.) +### Sending user inputs to `WebElement` + +To send user inputs to `WebElement`s, you can use the `sendKeys()` method, which simulates typing into text-based input fields (like textboxes, textareas, etc.). + +```ballerina +selenium:WebElement element = check driver.findByName("email"); +check element.sendKeys("exmaple@abc.com"); +``` + +### Performing Click event + +To perform a click event on a `WebElement`, you can use the `click()` method on the `WebElement` object. This is commonly used to simulate clicking on buttons, links, checkboxes, radio buttons, or any other clickable elements. + +```ballerina +selenium:WebElement element = check driver.findByClassName("submit-btn"); +check element.click(); +``` + +### Quit the driver + +```ballerina +check driver.quit(); +``` + +### Working with windows and tabs + +**Window handles** are unique identifiers for each browser window or tab. These can be used to programmatically manage multiple windows. + +```ballerina +// Initialize a new WebDriver instance +selenium:WebDriver driver = new ({ + url: "https://the-internet.herokuapp.com/windows" +}); + +// Print the title of the current window. +io:println(driver.getTitle()); // Prints "The Internet" + +// Store the current window handle. +string firstWindow = check driver.getCurrentWindowHandle(); + +// Click on the link that opens a new window/tab. +check (check driver.findByPartialLinkText("Click")).click(); + +// Get handles for all open windows. Currently, there are 2 windows open, +// so this array contains the handles of those 2 windows in the order they were opened. +string[] allWindows = check driver.getAllWindowHandles(); + +// Even though we opened a new window, the driver is still pointing to the first window. To interact with the new window, we need to switch to it using the window handle. +check driver.switchToWindowHandle(allWindows[1]); + +// Print the title of the currrent window. +io:println(driver.getTitle()); // Prints "New Window" + +// Close the second (current) window. +check driver.closeCurrentWindowHandle(); + +// Switch back to the first window. Now the driver is pointing to the first window. +check driver.switchToWindowHandle(firstWindow); + +// Print the title of the current window +io:println(driver.getTitle()); // Prints "The Internet" + +// Quit the driver and close all windows +check driver.quit(); +``` + +### Selenium IDE for Finding Locators + +Selenium IDE is a handy tool for finding locators for web elements. Here's how to use it effectively: + +1. Install Selenium IDE: Download and install the Selenium IDE extension for your browser (Chrome or Firefox) [here](https://www.selenium.dev/selenium-ide/). +2. Launch Selenium IDE: Open the Selenium IDE extension from your browser's toolbar. + +3. Start a New Project: Click on Create a New Project and name your project. + + + +4. Start Recording: + - Click on the Record a New Test in a New Project option. + - Enter the URL of the web application you want to test. + - Selenium IDE will open the URL in a new tab and start recording your actions. + + + +5. Perform Actions on the Webpage: Interact with the web elements (e.g., click buttons, fill out forms) on the page. Selenium IDE will record these actions as steps in your test case. +6. View Recorded Steps: Once done, stop the recording. The recorded steps will appear in the Selenium IDE window. Each step will include information about the action and the locator used. +7. Inspect Locators: + - In the list of recorded steps, click on a step to view details. + - The Target field shows the locator for the web element (e.g., XPath, ID, Name, CSS selector, etc.). + - You can switch between different locator strategies by clicking the dropdown next to the locator. Selenium IDE will display alternatives if available. + + ## Examples The `Selenium` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/xlibb/module-selenium/tree/main/examples/), covering the following use cases: -[//]: # (TODO: Add examples) +1. [Web Scrapping.](https://github.com/xlibb/module-selenium/tree/main/examples/web_scrapping) + +2. [Automating the Filling of Student Application Forms for a Web Application.](https://github.com/xlibb/module-selenium/tree/main/examples/student_application_form_filling) diff --git a/ballerina/Package.md b/ballerina/Package.md index 1f4ba23..3af86da 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -1,17 +1,210 @@ ## Overview -[//]: # (TODO: Add overview mentioning the purpose of the module, supported REST API versions, and other high-level details.) +This module automates web applications across various browsers. Selenium interacts with web browsers directly, simulating user actions such as clicks, text input, page navigation, and more. -## Setup guide +## Quickstart -[//]: # (TODO: Add detailed steps to obtain credentials and configure the module.) +### Import the module -## Quickstart +```ballerina +import xlibb/selenium; +``` + +### Create a new web driver instance + +The core component of Selenium is the `WebDriver`. It is an interface for controlling a web browser instance and communicates with the browser through its native API. + +The constructor of the `WebDriver` takes `BrowserOptions` as an argument, which contains the following options: + +1. `browserName` - A enum value specifies the type of browser to open. Acceptable values are `selenium:CHROME` or `selenium:FIREFOX`. The default is `selenium:CHROME`. +2. `url` - The URL of the web application to open in the browser. +3. `headlessMode` - A boolean value indicating whether to run the browser in headless mode (without a GUI). The default is `false`. +4. `incognitoMode` - A boolean value indicating whether to run the browser in incognito mode. The default is `false`. +5. `additionalArguments` - A list of additional command-line arguments to pass to the browser during initialization. + +```ballerina +// Opens a new Chrome browser instance and navigates to the specified URL. +selenium:WebDriver driver = new ({ + url: "https://central.ballerina.io/" +}); +``` + +### Locating elements + +`WebElement` in `selenium` represents an element in the DOM, which allows interaction (e.g., click, send keys). + +A locator is a way to identify `WebElement` on a page. Selenium provides support for several location strategies(locators) in WebDriver: + +- **class name**: Locates elements by the value of their `class` attribute. +- **ID**: Locates elements by the value of their `id` attribute. +- **CSS selector**: Locates elements using CSS selectors, which are patterns used to select elements based on their attributes. +- **name**: Locates elements by the value of their `name` attribute. +- **tag name**: Locates elements by their tag name, such as `input`, `div`, etc. +- **XPath**: Locates elements using XPath expressions, which are used to navigate through elements and attributes in an XML document. +- **link text**: Locates anchor elements by the exact text they display. +- **partial link text**: Locates anchor elements by a partial match of the text they display. + +```ballerina +import xlibb/selenium; +import ballerina/io; + +public function main() { + // By ID + selenium:WebElement form = check driver.findById("form"); + + // By Name + selenium:WebElement searchboxByName = check driver.findByName("q"); + + // By CSS Selector + selenium:WebElement searchboxByCss = check driver.findByCssSelector("#form .search-box"); + + // By XPath + selenium:WebElement searchboxByXpath = check driver.findByXpath("//input[@id='search']"); + + // By Class Name + selenium:WebElement searchboxByClassName = check driver.findByClassName("search-box"); + + // By Tag Name + selenium:WebElement searchboxByTagName = check driver.findByTagName("input"); + + // Locating a WebElement inside another WebElement + selenium:WebElement nestedElement = check form.findByName("q"); + + // Locating all elements with same locator + selenium:WebElement[] inputElements = check driver.findAllByTagName("input"); +} + +``` + +### Fetching data over any `WebElement` + +To fetch data from a `WebElement` in Selenium, you can interact with it using various methods depending on the type of data you need. + +1. Text of an element: + +The `getText()` method in Selenium is used to retrieve the visible (i.e., not hidden by CSS) inner text of a `WebElement`. This method is commonly used to extract text from elements such as paragraphs, headings, labels, or any other HTML elements that contain text. + +```ballerina +selenium:WebElement label = check driver.findByClassName("input-label"); +string labelText = check label.getText(); +io:println(labelText); +``` + +2. Attribute value + +The `getDomAttribute` method in Selenium is used to retrieve the value of a specified attribute from a `WebElement`. This method is particularly useful when you need to access the underlying attributes of an element in the DOM (Document Object Model) that are not directly visible on the page. Commonly used to get attributes like value for input fields, href for links, src for images, etc. + +```ballerina +string value = (check driver.findElementById("elementId")).getDomAttribute("value"); // For input fields +string href = (check driver.findElementByTagName("a")).getDomAttribute("href"); // For links +``` + +3. Checking if an element is displayed/enabled/selected: + +```ballerina +selenium:WebElement element = check driver.findById("elementId"); +boolean isDisplayed = check element.isDisplayed(); +boolean isEnabled = check element.isEnabled(); +boolean isSelected = check element.isSelected(); +``` -[//]: # (TODO: Add a quickstart guide to demonstrate a basic functionality of the module, including sample code snippets.) +### Sending user inputs to `WebElement` + +To send user inputs to `WebElement`s, you can use the `sendKeys()` method, which simulates typing into text-based input fields (like textboxes, textareas, etc.). + +```ballerina +selenium:WebElement element = check driver.findByName("email"); +check element.sendKeys("exmaple@abc.com"); +``` + +### Performing Click event + +To perform a click event on a `WebElement`, you can use the `click()` method on the `WebElement` object. This is commonly used to simulate clicking on buttons, links, checkboxes, radio buttons, or any other clickable elements. + +```ballerina +selenium:WebElement element = check driver.findByClassName("submit-btn"); +check element.click(); +``` + +### Quit the driver + +```ballerina +check driver.quit(); +``` + +### Working with windows and tabs + +**Window handles** are unique identifiers for each browser window or tab. These can be used to programmatically manage multiple windows. + +```ballerina +// Initialize a new WebDriver instance +selenium:WebDriver driver = new ({ + url: "https://the-internet.herokuapp.com/windows" +}); + +// Print the title of the current window. +io:println(driver.getTitle()); // Prints "The Internet" + +// Store the current window handle. +string firstWindow = check driver.getCurrentWindowHandle(); + +// Click on the link that opens a new window/tab. +check (check driver.findByPartialLinkText("Click")).click(); + +// Get handles for all open windows. Currently, there are 2 windows open, +// so this array contains the handles of those 2 windows in the order they were opened. +string[] allWindows = check driver.getAllWindowHandles(); + +// Even though we opened a new window, the driver is still pointing to the first window. To interact with the new window, we need to switch to it using the window handle. +check driver.switchToWindowHandle(allWindows[1]); + +// Print the title of the currrent window. +io:println(driver.getTitle()); // Prints "New Window" + +// Close the second (current) window. +check driver.closeCurrentWindowHandle(); + +// Switch back to the first window. Now the driver is pointing to the first window. +check driver.switchToWindowHandle(firstWindow); + +// Print the title of the current window +io:println(driver.getTitle()); // Prints "The Internet" + +// Quit the driver and close all windows +check driver.quit(); +``` + +### Selenium IDE for Finding Locators + +Selenium IDE is a handy tool for finding locators for web elements. Here's how to use it effectively: + +1. Install Selenium IDE: Download and install the Selenium IDE extension for your browser (Chrome or Firefox) [here](https://www.selenium.dev/selenium-ide/). +2. Launch Selenium IDE: Open the Selenium IDE extension from your browser's toolbar. + +3. Start a New Project: Click on Create a New Project and name your project. + + + +4. Start Recording: + - Click on the Record a New Test in a New Project option. + - Enter the URL of the web application you want to test. + - Selenium IDE will open the URL in a new tab and start recording your actions. + + + +5. Perform Actions on the Webpage: Interact with the web elements (e.g., click buttons, fill out forms) on the page. Selenium IDE will record these actions as steps in your test case. +6. View Recorded Steps: Once done, stop the recording. The recorded steps will appear in the Selenium IDE window. Each step will include information about the action and the locator used. +7. Inspect Locators: + - In the list of recorded steps, click on a step to view details. + - The Target field shows the locator for the web element (e.g., XPath, ID, Name, CSS selector, etc.). + - You can switch between different locator strategies by clicking the dropdown next to the locator. Selenium IDE will display alternatives if available. + + ## Examples The `Selenium` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/xlibb/module-selenium/tree/main/examples/), covering the following use cases: -[//]: # (TODO: Add examples) +1. [Web Scrapping.](https://github.com/xlibb/module-selenium/tree/main/examples/web_scrapping) + +2. [Automating the Filling of Student Application Forms for a Web Application.](https://github.com/xlibb/module-selenium/tree/main/examples/student_application_form_filling) diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 5a8e176..66fbb27 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -19,10 +19,10 @@ import org.apache.tools.ant.taskdefs.condition.Os plugins { - id 'io.ballerina.plugin' + id "io.ballerina.plugin" } -description = 'selenium - Ballerina' +description = 'Ballerina - Selenium Package' def packageName = "selenium" def packageOrg = "xlibb" @@ -44,18 +44,27 @@ def stripBallerinaExtensionVersion(String extVersion) { } } +apply plugin: 'io.ballerina.plugin' + ballerina { packageOrganization = packageOrg module = packageName - testCoverageParam = "--code-coverage --coverage-format=xml" - isConnector = true - platform = "any" + langVersion = ballerinaLangVersion + testCoverageParam = "--code-coverage --coverage-format=xml --includes=io.xlibb.selenium.*:xlibb.selenium*" + platform = "java17" +} + +dependencies { + jbalTools("org.ballerinalang:jballerina-tools:${ballerinaLangVersion}") { + transitive = false + } } task updateTomlFiles { doLast { def newBallerinaToml = ballerinaTomlFilePlaceHolder.text.replace("@project.version@", project.version) newBallerinaToml = newBallerinaToml.replace("@toml.version@", tomlVersion) + newBallerinaToml = newBallerinaToml.replace("@selenium.version@", project.seleniumVersion) ballerinaTomlFile.text = newBallerinaToml } } @@ -65,9 +74,9 @@ task commitTomlFiles { project.exec { ignoreExitValue true if (Os.isFamily(Os.FAMILY_WINDOWS)) { - commandLine 'cmd', '/c', "git commit -m \"[Automated] Update the toml files\" Ballerina.toml Dependencies.toml" + commandLine 'cmd', '/c', "git commit -m \"[Automated] Update the native jar versions\" Ballerina.toml Dependencies.toml" } else { - commandLine 'sh', '-c', "git commit -m '[Automated] Update the toml files' Ballerina.toml Dependencies.toml" + commandLine 'sh', '-c', "git commit -m '[Automated] Update the native jar versions' Ballerina.toml Dependencies.toml" } } } @@ -79,10 +88,11 @@ publishing { artifact source: createArtifactZip, extension: 'zip' } } + repositories { maven { name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/ballerina-platform/module-${packageOrg}-${packageName}") + url = uri("https://maven.pkg.github.com/xlibb/module-selenium") credentials { username = System.getenv("publishUser") password = System.getenv("publishPAT") @@ -91,10 +101,10 @@ publishing { } } -clean { - delete 'build' -} +updateTomlFiles.dependsOn copyStdlibs +test.dependsOn ":selenium-native:build" +build.dependsOn ":selenium-native:build" build.dependsOn "generatePomFileForMavenPublication" publishToMavenLocal.dependsOn build publish.dependsOn build diff --git a/ballerina/errors.bal b/ballerina/errors.bal new file mode 100644 index 0000000..97f0361 --- /dev/null +++ b/ballerina/errors.bal @@ -0,0 +1,62 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +# Represents any error related to the Selenium module. +public type Error distinct error; + +# Represents an error that occurs when performing actions on an element +# that is in an invalid state (e.g., a disabled input field). +public type InvalidElementStateError distinct Error; + +# Represents an error that occurs due to an insecure or untrusted SSL certificate. +public type InsecureCertificateError distinct Error; + +# Represents an error that occurs when an invalid argument is passed to a command. +public type InvalidArgumentError distinct Error; + +# Represents an error that occurs when attempting to locate or interact with an element using an invalid selector. +public type InvalidSelectorError distinct Error; + +# Represents an error that occurs when executing JavaScript code in the browser. +public type JavascriptError distinct Error; + +# Represents an error that occurs when attempting to interact with an alert that is not present. +public type NoAlertPresentError distinct Error; + +# Represents an error that occurs when an element could not be found in the DOM. +public type NoSuchElementError distinct Error; + +# Represents an error that occurs when attempting to switch to a window that no longer exists. +public type NoSuchWindowError distinct Error; + +# Represents an error that occurs when a server request fails unexpectedly. +public type RequestFailedError distinct Error; + +# Represents an error that occurs when an element's reference is stale +# (e.g., the element is no longer attached to the DOM). +public type StaleElementReferenceError distinct Error; + +# Represents an error that occurs when a session could not be created. +public type SessionNotCreatedError distinct Error; + +# Represents an error that occurs when a script execution takes longer than the specified timeout. +public type ScriptTimeoutError distinct Error; + +# Represents an error that occurs when an operation exceeds the specified timeout period. +public type TimeoutError distinct Error; + +# Represents an error that occurs when an unexpected alert is present during an operation. +public type UnhandledAlertError distinct Error; diff --git a/ballerina/icon.png b/ballerina/icon.png new file mode 100644 index 0000000..4ee5b06 Binary files /dev/null and b/ballerina/icon.png differ diff --git a/ballerina/client.bal b/ballerina/init.bal similarity index 70% rename from ballerina/client.bal rename to ballerina/init.bal index 66cdc3f..69543a9 100644 --- a/ballerina/client.bal +++ b/ballerina/init.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except @@ -13,3 +13,13 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. + +import ballerina/jballerina.java; + +isolated function init() { + setModule(); +} + +isolated function setModule() = @java:Method { + 'class: "io.xlibb.selenium.utils.ModuleUtils" +} external; diff --git a/ballerina/resources/enter base url - side.png b/ballerina/resources/enter base url - side.png new file mode 100644 index 0000000..4d7c9f3 Binary files /dev/null and b/ballerina/resources/enter base url - side.png differ diff --git a/ballerina/resources/find locators.png b/ballerina/resources/find locators.png new file mode 100644 index 0000000..5b42e18 Binary files /dev/null and b/ballerina/resources/find locators.png differ diff --git a/ballerina/resources/new project - side.png b/ballerina/resources/new project - side.png new file mode 100644 index 0000000..70963fb Binary files /dev/null and b/ballerina/resources/new project - side.png differ diff --git a/ballerina/resources/start recording - side.png b/ballerina/resources/start recording - side.png new file mode 100644 index 0000000..0efa57e Binary files /dev/null and b/ballerina/resources/start recording - side.png differ diff --git a/ballerina/types.bal b/ballerina/types.bal new file mode 100644 index 0000000..55a32f4 --- /dev/null +++ b/ballerina/types.bal @@ -0,0 +1,36 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +# Represents name of browser to open. +public enum BrowserName { + CHROME = "chrome", + FIREFOX = "firefox" +} + +# Represents the options for configuring the WebDriver instance. +# +# + browserName - The type of browser to open (either "chrome" or "firefox"). The default is "chrome". +# + url - The URL of the web application to open in the browser. +# + headlessMode - A boolean value indicating whether to run the browser in headless mode (without a GUI). The default is `false`. +# + incognitoMode - A boolean value indicating whether to run the browser in incognito mode. The default is `false`. +# + additionalArguments - A list of additional command-line arguments to pass to the browser during initialization. +public type BrowserOptions record {| + BrowserName browserName = CHROME; + string url; + boolean headlessMode = false; + boolean incognitoMode = false; + string[] additionalArguments = []; +|}; diff --git a/ballerina/web_driver.bal b/ballerina/web_driver.bal new file mode 100644 index 0000000..90e7ab6 --- /dev/null +++ b/ballerina/web_driver.bal @@ -0,0 +1,317 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/jballerina.java; + +# Consists of APIs to interact with web browsers. +public isolated class WebDriver { + + public isolated function init(BrowserOptions options) returns Error? { + match options.browserName { + CHROME => { + return check self.openChrome(options); + } + FIREFOX => { + return check self.openFirefox(options); + } + } + } + + # Opens a new Chrome browser instance and navigates to the specified URL. + # + # + options - Represents options for configuring the WebDriver instance. + # + return - Returns an `Error` if the browser fails to open or navigate to the specified URL, otherwise returns `()`. + private isolated function openChrome(BrowserOptions options) returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Opens a new Firefox browser instance and navigates to the specified URL. + # + # + options - Represents options for configuring the WebDriver instance. + # + return - Returns an `Error` if the browser fails to open or navigate to the specified URL, otherwise returns `()`. + private isolated function openFirefox(BrowserOptions options) returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Retrieves the title of the current web page opened in the browser. + # + # + return - Returns a `string` representing the title of the web page, or an `Error` if an error occurs. + public isolated function getTitle() returns string|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Maximizes the browser window to fill the screen. + # + # + return - Returns `()` if the operation is successful, otherwise an `Error`. + public isolated function maximize() returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Minimizes the browser window. + # + # + return - Returns `()` if the operation is successful, otherwise an `Error`. + public isolated function minimize() returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Sets the size of the browser window to the specified width and height. + # + # + width - The desired width of the browser window in pixels. + # + height - The desired height of the browser window in pixels. + # + return - Returns `()` if the operation is successful, otherwise an `Error`. + public isolated function setSize(int width, int height) returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Navigates the browser to the specified URL. + # + # + url - The URL to navigate to. + # + return - Returns `()` if the operation is successful, otherwise an `Error`. + public isolated function navigateTo(string url) returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Navigates the browser back to the previous page in the browsing history. + # + # + return - Returns `()` if the operation is successful, otherwise an `Error`. + public isolated function navigateBack() returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Navigates the browser forward to the next page in the browsing history (if available). + # + # + return - Returns `()` if the operation is successful, otherwise an `Error`. + public isolated function navigateForward() returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Refreshes the current web page in the browser. + # + # + return - Returns `()` if the operation is successful, otherwise an `Error`. + public isolated function refresh() returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Retrieves the current URL of the web page opened in the browser. + # + # + return - Returns a `string` representing the current URL, or an `Error` if an error occurs. + public isolated function getCurrentUrl() returns string|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds an HTML element on the web page using its unique `id` attribute. + # + # + id - The unique `id` attribute of the HTML element to find. + # + return - A `WebElement` representing the found element, or an `Error` if the element is not found. + public isolated function findById(string id) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds an HTML element on the web page using its `class` attribute. + # + # + className - The `class` attribute of the HTML element to find. + # + return - A `WebElement` representing the found element, or an `Error` if the element is not found. + public isolated function findByClassName(string className) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds an HTML element on the web page using its tag name. + # + # + tagName - The tag name of the HTML element to find (e.g., "div", "input"). + # + return - A `WebElement` representing the found element, or an `Error` if the element is not found. + public isolated function findByTagName(string tagName) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds an HTML element on the web page using an XPath expression. + # + # + xpath - The XPath expression used to locate the HTML element. + # + return - A `WebElement` representing the found element, or an `Error` if the element is not found. + public isolated function findByXpath(string xpath) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds an HTML element on the web page using a CSS selector. + # + # + cssSelector - The CSS selector used to locate the HTML element. + # + return - A `WebElement` representing the found element, or an `Error` if the element is not found. + public isolated function findByCssSelector(string cssSelector) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds an HTML element on the web page using its name attribute. + # + # + name - The `name` attribute of the HTML element to find. + # + return - A `WebElement` representing the found element, or an `Error` if the element is not found. + public isolated function findByName(string name) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds an HTML element on the web page using the visible text of a link. + # + # + linkText - The exact visible text of the link element to find. + # + return - A `WebElement` representing the found link element, or an `Error` if the element is not found. + public isolated function findByLinkText(string linkText) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds an HTML element on the web page using partial visible text of a link. + # + # + partialLinkText - The partial visible text of the link element to find. + # + return - A `WebElement` representing the found link element, or an `Error` if the element is not found. + public isolated function findByPartialLinkText(string partialLinkText) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds all HTML elements on the web page with the specified class name. + # + # + className - The `class` attribute of the HTML elements to find. + # + return - Returns `WebElement[]` representing the found elements, or an `Error` if an error occurs. + public isolated function findAllByClassName(string className) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds all HTML elements on the web page with the specified tag name. + # + # + tagName - The tag name of the HTML elements to find (e.g., "div", "input"). + # + return - Returns `WebElement[]` representing the found elements, or an `Error` if an error occurs. + public isolated function findAllByTagName(string tagName) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds all HTML elements on the web page using an XPath expression. + # + # + xpath - The XPath expression used to locate the HTML elements. + # + return - Returns `WebElement[]` representing the found elements, or an `Error` if an error occurs. + public isolated function findAllByXpath(string xpath) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds all HTML elements on the web page using a CSS selector. + # + # + cssSelector - The CSS selector used to locate the HTML elements. + # + return - Returns `WebElement[]` representing the found elements, or an `Error` if an error occurs. + public isolated function findAllByCssSelector(string cssSelector) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds all HTML elements on the web page using their name attribute. + # + # + name - The `name` attribute of the HTML elements to find. + # + return - Returns `WebElement[]` representing the found elements, or an `Error` if an error occurs. + public isolated function findAllByName(string name) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds all HTML link elements on the web page using the exact visible text. + # + # + linkText - The exact visible text of the link elements to find. + # + return - Returns `WebElement[]` representing the found elements, or an `Error` if an error occurs. + public isolated function findAllByLinkText(string linkText) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Finds all HTML link elements on the web page using partial visible text. + # + # + partialLinkText - The partial visible text of the link elements to find. + # + return - Returns `WebElement[]` representing the found elements, or an `Error` if an error occurs. + public isolated function findAllByPartialLinkText(string partialLinkText) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Accepts an alert popup in the browser. + # + # This method clicks the "OK" button on an alert dialog box (e.g., JavaScript alerts, confirmations, or prompts). + # + # + return - Returns `()` if the alert is accepted successfully, otherwise an `Error`. + public isolated function acceptAlert() returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Dismisses an alert popup in the browser. + # + # This method clicks the "Cancel" button or dismisses an alert dialog box (e.g., JavaScript alerts, confirmations, or prompts). + # + # + return - Returns an `()` if the alert is dismissed successfully, otherwise an `Error`. + public isolated function dismissAlert() returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Sends input text to an alert popup with a text box in the browser. + # + # This method inputs a specified value into a prompt alert dialog box. Note that this method only works on alerts that accept user input. + # + # + value - The input text to send to the alert's text box. + # + return - Returns `()` if the input is sent successfully, otherwise an `Error`. + public isolated function sendKeysToAlert(string value) returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Retrieves the handle of the current active browser window. + # + # The handle is a unique identifier for the currently active browser window. + # + # + return - Returns a `string` representing the window handle, or an `Error` if an error occurs. + public isolated function getCurrentWindowHandle() returns string|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Retrieves the handles of all open browser windows. + # + # The method returns a list of unique identifiers for all the currently open browser windows. + # + # + return - Returns a `string[]` representing the window handles, or an `Error` if an error occurs. + public isolated function getAllWindowHandles() returns string[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Switches the context to the specified browser window handle. + # + # This method allows switching focus to a specific browser window identified by its handle. + # + # + windowHandle - The unique identifier of the browser window to switch to. + # + return - Returns an `()` if the switch is successful, otherwise an `Error`. + public isolated function switchToWindowHandle(string windowHandle) returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Closes the currently active browser window. + # + # This method closes the window that is currently in focus. If there are multiple windows open, the other windows will remain open. + # + # + return - Returns `()` if the window is closed successfully, otherwise an `Error`. + public isolated function closeCurrentWindowHandle() returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Executes a JavaScript script on the current page in the Selenium WebDriver session. + # + # + script - The JavaScript script to execute. + # + return - Returns `()` if the script is executed successfully, otherwise an `Error`. + public isolated function executeJavascript(string script) returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + + # Quits the browser session and closes all open browser windows. + # + # This method ends the current WebDriver session and closes all associated browser windows. + # + # + return - Returns `()` if the session is closed successfully, otherwise an `Error`. + public isolated function quit() returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebDriver" + } external; + +} diff --git a/ballerina/web_element.bal b/ballerina/web_element.bal new file mode 100644 index 0000000..e465187 --- /dev/null +++ b/ballerina/web_element.bal @@ -0,0 +1,207 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/jballerina.java; + +# Consists of APIs to interact with web elements. +public isolated class WebElement { + + # Performs a click action on the specified web element. + # + # + return - Returns `()` if the click action is successful, otherwise an `Error`. + public isolated function click() returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Clears the value of an input field or text area. + # + # + return - Returns `()` if the clear action is successful, otherwise an `Error`. + public isolated function clear() returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Sends input text to the specified web element. + # + # + value - The text to input into the web element. + # + return - Returns `()` if the text input is successful, otherwise an `Error`. + public isolated function sendKeys(string value) returns Error? = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Retrieves the tag name of the web element. + # + # + return - Returns `string` representing the tag name of the web element, or `Error` if an error occurs. + public isolated function getTagName() returns string|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Retrieves the visible text of the web element. + # + # + return - Returns `string` containing the visible text of the element, or `Error` if an error occurs. + public isolated function getText() returns string|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Retrieves the value of a specified DOM attribute for a web element. + # + # + attribute - The name of the attribute whose value needs to be fetched (e.g., `href`, `src`, `class`, etc.). + # + return - Returns string representing the value of the specified DOM attribute, or an `Error` if the attribute is not found. + public isolated function getDomAttribute(string attribute) returns string|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Checks if the web element is displayed on the page. + # + # + return - Returns `boolean` value: `true` if the element is displayed, otherwise `false`. + public isolated function isDisplayed() returns boolean|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Checks if the web element is enabled. + # + # + return - Returns `boolean` value: `true` if the element is enabled, otherwise `false`. + public isolated function isEnabled() returns boolean|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Checks if the web element is selected. Typically used for checkboxes, radio buttons, or options in a dropdown. + # + # + return - Returns `boolean` value: `true` if the element is selected, otherwise `false`. + public isolated function isSelected() returns boolean|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates a web element by its unique ID attribute inside another web element. + # + # + id - The unique identifier of the web element. + # + return - Returns `WebElement` representing the located element, or an `Error` if the element cannot be found. + public isolated function findById(string id) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates a web element by its class name inside another web element. + # + # + className - The class name of the web element. + # + return - Returns `WebElement` representing the located element, or an `Error` if the element cannot be found. + public isolated function findByClassName(string className) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates a web element by its tag name inside another web element. + # + # + tagName - The tag name of the web element (e.g., `div`, `input`, `button`). + # + return - Returns `WebElement` representing the located element, or an `Error` if the element cannot be found. + public isolated function findByTagName(string tagName) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates a web element using an XPath expression inside another web element. + # + # + xpath - The XPath expression used to locate the element. + # + return - Returns `WebElement` representing the located element, or an `Error` if the element cannot be found. + public isolated function findByXpath(string xpath) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates a web element using a CSS selector inside another web element. + # + # + cssSelector - The CSS selector used to locate the element. + # + return - Returns `WebElement` representing the located element, or an `Error` if the element cannot be found. + public isolated function findByCssSelector(string cssSelector) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates a web element by its `name` attribute inside another web element. + # + # + name - The name attribute of the web element. + # + return - Returns `WebElement` representing the located element, or an `Error` if the element cannot be found. + public isolated function findByName(string name) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates a web element by its exact link text inside another web element. + # + # + linkText - The exact text of the link. + # + return - Returns `WebElement` representing the located element, or an `Error` if the element cannot be found. + public isolated function findByLinkText(string linkText) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates a web element by its partial link text inside another web element. + # + # + partialLinkText - The partial text of the link. + # + return - Returns `WebElement` representing the located element, or an `Error` if the element cannot be found. + public isolated function findByPartialLinkText(string partialLinkText) returns WebElement|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates all web elements by their class name. + # + # + className - The class name of the web elements. + # + return - Returns an array of `WebElement` representing all the located elements, or `Error` if an error occurs. + public isolated function findAllByClassName(string className) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates all web elements by their tag name inside another web element. + # + # + tagName - The tag name of the web elements (e.g., `div`, `input`, `button`). + # + return - Returns an array of `WebElement` representing all the located elements, or `Error` if an error occurs. + public isolated function findAllByTagName(string tagName) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates all web elements using an XPath expression inside another web element. + # + # + xpath - The XPath expression used to locate the elements. + # + return - Returns an array of `WebElement` representing all the located elements, or `Error` if an error occurs. + public isolated function findAllByXpath(string xpath) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates all web elements using a CSS selector inside another web element. + # + # + cssSelector - The CSS selector used to locate the elements. + # + return - Returns an array of `WebElement` representing all the located elements, or `Error` if an error occurs. + public isolated function findAllByCssSelector(string cssSelector) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates all web elements by their `name` attribute inside another web element. + # + # + name - The `name` attribute of the web elements. + # + return - Returns an array of `WebElement` representing all the located elements, or `Error` if an error occurs. + public isolated function findAllByName(string name) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates all web elements by their exact link text inside another web element. + # + # + linkText - The exact text of the links. + # + return - Returns an array of `WebElement` representing all the located elements, or `Error` if an error occurs. + public isolated function findAllByLinkText(string linkText) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + + # Locates all web elements by their partial link text inside another web element. + # + # + partialLinkText - The partial text of the links. + # + return - Returns an array of `WebElement` representing all the located elements, or `Error` if an error occurs. + public isolated function findAllByPartialLinkText(string partialLinkText) returns WebElement[]|Error = @java:Method { + 'class: "io.xlibb.selenium.SeleniumWebElement" + } external; + +} diff --git a/build-config/checkstyle/build.gradle b/build-config/checkstyle/build.gradle new file mode 100644 index 0000000..76708b2 --- /dev/null +++ b/build-config/checkstyle/build.gradle @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +plugins { + id "de.undercouch.download" +} + +apply plugin: 'java' + +task downloadCheckstyleRuleFiles(type: Download) { + src([ + 'https://raw.githubusercontent.com/wso2/code-quality-tools/v1.4/checkstyle/jdk-17/checkstyle.xml', + 'https://raw.githubusercontent.com/wso2/code-quality-tools/v1.4/checkstyle/jdk-17/suppressions.xml' + ]) + overwrite false + onlyIfNewer true + dest buildDir +} + +jar { + enabled = false +} + +clean { + enabled = false +} + +artifacts.add('default', file("$project.buildDir/checkstyle.xml")) { + builtBy('downloadCheckstyleRuleFiles') +} + +artifacts.add('default', file("$project.buildDir/suppressions.xml")) { + builtBy('downloadCheckstyleRuleFiles') +} diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 991c8e0..454ba8d 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -5,8 +5,8 @@ name = "selenium" version = "@toml.version@" license = ["Apache-2.0"] authors = ["Ballerina"] -keywords = [] # TODO: Add keywords -# icon = "icon.png" # TODO: Add icon +keywords = ["selenium", "web-automation"] +icon = "icon.png" repository = "https://github.com/xlibb/module-selenium" [build-options] @@ -14,3 +14,260 @@ observabilityIncluded = true [platform.java17] graalvmCompatible = true + +[[platform.java17.dependency]] +groupId = "io.xlibb.selenium" +artifactId = "selenium-native" +version = "0.1.0" +path = "../native/build/libs/selenium-native-0.1.0-SNAPSHOT.jar" + +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-java" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-api" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-api:4.27.0 +[[platform.java17.dependency]] +groupId = "org.jspecify" +artifactId = "jspecify" +version = "1.0.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-chrome-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-chrome-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.auto.service" +artifactId = "auto-service-annotations" +version = "1.1.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-chrome-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-chromium-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-chrome-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-json" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-chrome-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-manager" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-devtools-v129" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-devtools-v130" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-devtools-v131" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-devtools-v85" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-edge-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-firefox-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-firefox-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-http" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-firefox-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "dev.failsafe" +artifactId = "failsafe" +version = "3.3.2" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-ie-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-remote-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.guava" +artifactId = "guava" +version = "33.3.1-jre" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.guava" +artifactId = "failureaccess" +version = "1.0.2" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.guava" +artifactId = "listenablefuture" +version = "9999.0-empty-to-avoid-conflict-with-guava" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.code.findbugs" +artifactId = "jsr305" +version = "3.0.2" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.checkerframework" +artifactId = "checker-qual" +version = "3.43.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.errorprone" +artifactId = "error_prone_annotations" +version = "2.28.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "com.google.j2objc" +artifactId = "j2objc-annotations" +version = "3.0.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry.semconv" +artifactId = "opentelemetry-semconv" +version = "1.25.0-alpha" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-api" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-context" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-exporter-logging" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-common" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-extension-autoconfigure-spi" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-extension-autoconfigure" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-api-incubator" +version = "1.44.1-alpha" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-trace" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-metrics" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "io.opentelemetry" +artifactId = "opentelemetry-sdk-logs" +version = "1.44.1" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "net.bytebuddy" +artifactId = "byte-buddy" +version = "1.15.10" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-os" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-remote-driver:4.27.0 +[[platform.java17.dependency]] +groupId = "org.apache.commons" +artifactId = "commons-exec" +version = "1.4.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-safari-driver" +version = "4.27.0" + +# transitive dependency of org.seleniumhq.selenium:selenium-java:4.27.0 +[[platform.java17.dependency]] +groupId = "org.seleniumhq.selenium" +artifactId = "selenium-support" +version = "4.27.0" diff --git a/build.gradle b/build.gradle index a100f69..1ca5d5d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -18,6 +18,9 @@ plugins { id 'net.researchgate.release' + id "com.github.spotbugs-base" + id "com.github.johnrengelman.shadow" + id "de.undercouch.download" } allprojects { @@ -25,6 +28,7 @@ allprojects { version = project.version apply plugin: 'maven-publish' + apply plugin: 'jacoco' repositories { mavenLocal() @@ -58,7 +62,22 @@ allprojects { def moduleVersion = project.version.replace("-SNAPSHOT", "") task build { + dependsOn(':selenium-examples:build') dependsOn(':selenium-ballerina:build') + dependsOn(':selenium-native:build') +} + +subprojects { + configurations { + ballerinaStdLibs + jbalTools + } + dependencies { + jbalTools ("org.ballerinalang:jballerina-tools:${ballerinaLangVersion}") { + transitive = false + } + + } } release { diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 54c5f19..0000000 --- a/examples/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Examples - -The `xlibb/selenium` connector provides practical examples illustrating usage in various scenarios. - -[//]: # (TODO: Add examples) -1. -2. - -## Prerequisites - -[//]: # (TODO: Add prerequisites) - -## Running an example - -Execute the following commands to build an example from the source: - -* To build an example: - - ```bash - bal build - ``` - -* To run an example: - - ```bash - bal run - ``` - -## Building the examples with the local module - -**Warning**: Due to the absence of support for reading local repositories for single Ballerina files, the Bala of the module is manually written to the central repository as a workaround. Consequently, the bash script may modify your local Ballerina repositories. - -Execute the following commands to build all the examples against the changes you have made to the module locally: - -* To build all the examples: - - ```bash - ./build.sh build - ``` - -* To run all the examples: - - ```bash - ./build.sh run - ``` - \ No newline at end of file diff --git a/examples/build.gradle b/examples/build.gradle index 8d00ce9..6973139 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except diff --git a/examples/student_application_form_filling/Ballerina.toml b/examples/student_application_form_filling/Ballerina.toml new file mode 100644 index 0000000..b4fedc2 --- /dev/null +++ b/examples/student_application_form_filling/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "xlibb" +name = "student_application_form_filling" +version = "0.1.0" +distribution = "2201.10.2" + +[build-options] +observabilityIncluded = true diff --git a/examples/student_application_form_filling/Dependencies.toml b/examples/student_application_form_filling/Dependencies.toml new file mode 100644 index 0000000..e6c30f9 --- /dev/null +++ b/examples/student_application_form_filling/Dependencies.toml @@ -0,0 +1,92 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.10.2" + +[[package]] +org = "ballerina" +name = "io" +version = "1.6.3" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "observe" +version = "1.3.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerinai" +name = "observe" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "observe"} +] +modules = [ + {org = "ballerinai", packageName = "observe", moduleName = "observe"} +] + +[[package]] +org = "xlibb" +name = "selenium" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerinai", name = "observe"} +] +modules = [ + {org = "xlibb", packageName = "selenium", moduleName = "selenium"} +] + +[[package]] +org = "xlibb" +name = "student_application_form_filling" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerinai", name = "observe"}, + {org = "xlibb", name = "selenium"} +] +modules = [ + {org = "xlibb", packageName = "student_application_form_filling", moduleName = "student_application_form_filling"}, + {org = "xlibb", packageName = "student_application_form_filling", moduleName = "student_application_form_filling.resources"} +] + diff --git a/examples/student_application_form_filling/README.md b/examples/student_application_form_filling/README.md new file mode 100644 index 0000000..1f0f1be --- /dev/null +++ b/examples/student_application_form_filling/README.md @@ -0,0 +1,13 @@ +# Automating the filling of student application form + +This guide demonstrates automating the process of filling out student application forms using the Ballerina Selenium module. + +## Overview +The example project automates data entry by retrieving student information from a [JSON file](./modules/resources/data.json) and populating it into a [web application]((https://bal-selenium.choreoapps.dev/)). + +## Run the Example + +```ballerina +cd .\examples\student_application_form_filling +bal run +``` diff --git a/examples/student_application_form_filling/main.bal b/examples/student_application_form_filling/main.bal new file mode 100644 index 0000000..b133e5d --- /dev/null +++ b/examples/student_application_form_filling/main.bal @@ -0,0 +1,173 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/io; +import ballerina/lang.runtime; +import xlibb/selenium; + +public function main() returns error? { + + Data data = check getData(); + + selenium:WebDriver driver = check new ({ + url: "https://ballerina-ipa.choreoapps.dev/student-application" + }); + + check driver.maximize(); + + runtime:sleep(2); + + // 1. Personal details + selenium:WebElement fullNameElement = check driver.findById("fullName"); + check fullNameElement.sendKeys(data.fullName); + + selenium:WebElement nameWithInitialsElement = check driver.findById("nameWithInitials"); + check nameWithInitialsElement.sendKeys(data.nameWithInitials); + + selenium:WebElement dobElement = check driver.findById("dob"); + check dobElement.sendKeys(data.dob); + + selenium:WebElement ageElement = check driver.findById("age"); + check ageElement.sendKeys(data.age); + + selenium:WebElement nationalityElement = check driver.findById("nationality"); + check nationalityElement.sendKeys(data.nationality); + + selenium:WebElement genderElement = check driver.findById(data.gender); + check genderElement.click(); + + selenium:WebElement addressElement = check driver.findById("address"); + check addressElement.sendKeys(data.address); + + selenium:WebElement mobileElement = check driver.findById("mobile"); + check mobileElement.sendKeys(data.mobile); + + selenium:WebElement districtElement = check driver.findById("district"); + check districtElement.sendKeys(data.district); + + selenium:WebElement gramaSevakaElement = check driver.findById("gramaSevaka"); + check gramaSevakaElement.sendKeys(data.gramaSevaka); + + selenium:WebElement nicElement = check driver.findById("nic"); + check nicElement.sendKeys(data.nic ?: ""); + + selenium:WebElement passportElement = check driver.findById("passport"); + check passportElement.sendKeys(data.passport ?: ""); + + // 2. Emergency contact + selenium:WebElement emerNameElement = check driver.findById("emer-name"); + check emerNameElement.sendKeys(data.emergency.name); + + selenium:WebElement emerAddressElement = check driver.findById("emer-address"); + check emerAddressElement.sendKeys(data.emergency.address); + + selenium:WebElement emerMobileElement = check driver.findById("emer-mobile"); + check emerMobileElement.sendKeys(data.emergency.mobile); + + selenium:WebElement relationshipElement = check driver.findById("relationship"); + check relationshipElement.sendKeys(data.emergency.relationship); + + selenium:WebElement emerEmailElement = check driver.findById("emer-email"); + check emerEmailElement.sendKeys(data.emergency.email); + + // 3. O/L results + selenium:WebElement olSchoolElement = check driver.findById("ol-school"); + check olSchoolElement.sendKeys(data.olResults.school); + + selenium:WebElement olYearElement = check driver.findById("ol-year"); + check olYearElement.sendKeys(data.olResults.year); + + selenium:WebElement olIndexElement = check driver.findById("ol-index"); + check olIndexElement.sendKeys(data.olResults.index); + + selenium:WebElement olSubjectElement = check driver.findById("ol-subject"); + selenium:WebElement olGradeElement = check driver.findById("ol-grade"); + selenium:WebElement addOlResultElement = check driver.findById("add-ol-result"); + + foreach ResultsItem item in data.olResults.results { + check olSubjectElement.sendKeys(item.subject); + check olGradeElement.sendKeys(item.grade); + check addOlResultElement.click(); + } + + // 4. A/L results + selenium:WebElement alSchoolElement = check driver.findById("al-school"); + check alSchoolElement.sendKeys(data.alResults.school); + + selenium:WebElement alYearElement = check driver.findById("al-year"); + check alYearElement.sendKeys(data.alResults.year); + + selenium:WebElement alIndexElement = check driver.findById("al-index"); + check alIndexElement.sendKeys(data.alResults.index); + + selenium:WebElement zScoreElement = check driver.findById("zScore"); + check zScoreElement.sendKeys(data.alResults.zScore); + + selenium:WebElement alSubjectElement = check driver.findById("al-subject"); + selenium:WebElement alGradeElement = check driver.findById("al-grade"); + selenium:WebElement addAlResultElement = check driver.findById("add-al-result"); + foreach ResultsItem item in data.alResults.results { + check alSubjectElement.sendKeys(item.subject); + check alGradeElement.sendKeys(item.grade); + check addAlResultElement.click(); + } + + // 5. Other qualifications + selenium:WebElement courseElement = check driver.findById("course"); + selenium:WebElement nvqElement = check driver.findById("nvq"); + selenium:WebElement instituteElement = check driver.findById("institute"); + selenium:WebElement nvqYearElement = check driver.findById("nvq-year"); + selenium:WebElement nvqResultElement = check driver.findById("nvq-result"); + selenium:WebElement addNvqResultElement = check driver.findById("add-nvq-result"); + + foreach OtherQualificationsItem item in data.otherQualifications { + check courseElement.sendKeys(item.course); + check nvqElement.sendKeys(item.nvqLevel); + check instituteElement.sendKeys(item.institute); + check nvqYearElement.sendKeys(item.year); + check nvqResultElement.sendKeys(item.result); + check addNvqResultElement.click(); + } + + // 6. Extra curricular activities + selenium:WebElement extraActivitiesElement = check driver.findById("extra-activities"); + check extraActivitiesElement.sendKeys(data.extraCurricularActivities); + + // 7. References + selenium:WebElement refreeNameElement = check driver.findById("refree-name"); + selenium:WebElement designationElement = check driver.findById("designation"); + selenium:WebElement refreeAddressElement = check driver.findById("refree-address"); + selenium:WebElement refreeMobileElement = check driver.findById("refree-mobile"); + selenium:WebElement addRefreeElement = check driver.findById("add-refree"); + + foreach RefreesItem item in data.refrees { + check refreeNameElement.sendKeys(item.name); + check designationElement.sendKeys(item.designation); + check refreeAddressElement.sendKeys(item.address); + check refreeMobileElement.sendKeys(item.mobile); + check addRefreeElement.click(); + } + + // submit button + selenium:WebElement submitButtonElement = check driver.findById("submit"); + check submitButtonElement.click(); + + // close the browser + check driver.quit(); + + io:println("Data entered successfully!"); + +} diff --git a/examples/student_application_form_filling/modules/resources/data.json b/examples/student_application_form_filling/modules/resources/data.json new file mode 100644 index 0000000..d32cf8f --- /dev/null +++ b/examples/student_application_form_filling/modules/resources/data.json @@ -0,0 +1,84 @@ +{ + "fullName": "John Doe", + "nameWithInitials": "J. Doe", + "dob": "1990-01-01", + "age": "34", + "nationality": "American", + "gender": "male", + "address": "123 Main Street, Springfield", + "mobile": "+1234567890", + "district": "Springfield", + "gramaSevaka": "John Smith", + "nic": "123456789V", + "passport": "A1234567", + "emergency": { + "name": "Jane Doe", + "address": "456 Elm Street, Springfield", + "mobile": "+0987654321", + "relationship": "Spouse", + "email": "jane.doe@example.com" + }, + "olResults": { + "school": "Springfield High School", + "year": "2007", + "index": "OL12345", + "results": [ + { + "subject": "Mathematics", + "grade": "A" + }, + { + "subject": "Science", + "grade": "B" + }, + { + "subject": "English", + "grade": "A" + } + ] + }, + "alResults": { + "school": "Springfield High School", + "year": "2010", + "index": "AL54321", + "zScore": "1.75", + "results": [ + { + "subject": "Physics", + "grade": "B" + }, + { + "subject": "Chemistry", + "grade": "B" + }, + { + "subject": "Mathematics", + "grade": "A" + } + ] + }, + "otherQualifications": [ + { + "course": "Diploma in IT", + "nvqLevel": "4", + "institute": "Tech Academy", + "year": "2012", + "result": "Pass" + } + ], + "extraCurricularActivities": "Captain of school football team", + "refrees": [ + { + "name": "Mr. Mark Smith", + "designation": "Manager", + "address": "789 Pine Street, Springfield", + "mobile": "+1122334455" + }, + { + "name": "Ms. Lisa Johnson", + "designation": "Lecturer", + "address": "456 Oak Street, Springfield", + "mobile": "+2233445566" + } + ] +} diff --git a/examples/student_application_form_filling/records.bal b/examples/student_application_form_filling/records.bal new file mode 100644 index 0000000..13006f8 --- /dev/null +++ b/examples/student_application_form_filling/records.bal @@ -0,0 +1,79 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +type Emergency record { + string name; + string address; + string mobile; + string relationship; + string email; +}; + +type ResultsItem record { + string subject; + string grade; +}; + +type OlResults record { + string school; + string year; + string index; + ResultsItem[] results; +}; + +type AlResults record { + string school; + string year; + string index; + string zScore; + ResultsItem[] results; +}; + +type OtherQualificationsItem record { + string course; + string nvqLevel; + string institute; + string year; + string result; +}; + +type RefreesItem record { + string name; + string designation; + string address; + string mobile; +}; + +type Data record { + string fullName; + string nameWithInitials; + string dob; + string age; + string nationality; + string gender; + string address; + string mobile; + string district; + string gramaSevaka; + string|() nic; + string|() passport; + Emergency emergency; + OlResults olResults; + AlResults alResults; + OtherQualificationsItem[] otherQualifications; + string extraCurricularActivities; + RefreesItem[] refrees; +}; diff --git a/examples/student_application_form_filling/utils.bal b/examples/student_application_form_filling/utils.bal new file mode 100644 index 0000000..5344b59 --- /dev/null +++ b/examples/student_application_form_filling/utils.bal @@ -0,0 +1,23 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/io; + +public function getData() returns Data|error { + json inputs = check io:fileReadJson("modules/resources/data.json"); + Data data = check inputs.cloneWithType(); + return data; +} diff --git a/examples/web_scrapping/Ballerina.toml b/examples/web_scrapping/Ballerina.toml new file mode 100644 index 0000000..83487a7 --- /dev/null +++ b/examples/web_scrapping/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "dharshi" +name = "web_scrapping" +version = "0.1.0" +distribution = "2201.10.2" + +[build-options] +observabilityIncluded = true diff --git a/examples/web_scrapping/Dependencies.toml b/examples/web_scrapping/Dependencies.toml new file mode 100644 index 0000000..9e35f81 --- /dev/null +++ b/examples/web_scrapping/Dependencies.toml @@ -0,0 +1,91 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.10.2" + +[[package]] +org = "ballerina" +name = "io" +version = "1.6.3" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "observe" +version = "1.3.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerinai" +name = "observe" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "observe"} +] +modules = [ + {org = "ballerinai", packageName = "observe", moduleName = "observe"} +] + +[[package]] +org = "dharshi" +name = "web_scrapping" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerinai", name = "observe"}, + {org = "xlibb", name = "selenium"} +] +modules = [ + {org = "dharshi", packageName = "web_scrapping", moduleName = "web_scrapping"} +] + +[[package]] +org = "xlibb" +name = "selenium" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerinai", name = "observe"} +] +modules = [ + {org = "xlibb", packageName = "selenium", moduleName = "selenium"} +] + diff --git a/examples/web_scrapping/README.md b/examples/web_scrapping/README.md new file mode 100644 index 0000000..5d7aa48 --- /dev/null +++ b/examples/web_scrapping/README.md @@ -0,0 +1,11 @@ +# Web scrapping + +## Overview +Web scraping is an automated method for extracting large amounts of data from websites. It is one of the most efficient and effective ways to gather data from online sources. This guide demonstrates how to collect information about books from the website http://books.toscrape.com/ using the Selenium module. + +## Run the example + +```ballerina +cd .\examples\web_scrapping +bal run +``` diff --git a/examples/web_scrapping/main.bal b/examples/web_scrapping/main.bal new file mode 100644 index 0000000..33da84c --- /dev/null +++ b/examples/web_scrapping/main.bal @@ -0,0 +1,63 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/io; +import ballerina/lang.runtime; + +import xlibb/selenium; + +public function main() returns error? { + + selenium:WebDriver driver = check new ({ + url: "http://books.toscrape.com/" + }); + check driver.maximize(); + runtime:sleep(2); + + string category = "Science"; + selenium:WebElement|error categoryLink = driver.findByPartialLinkText(category); + if categoryLink is error { + io:println(`Invalid category name: ${category}`); + check driver.quit(); + return; + } + check categoryLink.click(); + runtime:sleep(2); + + selenium:WebElement[] results = check driver.findAllByClassName("product_pod"); + if results.length() == 0 { + io:println("No results found for category " + category); + } + int count = 0; + foreach selenium:WebElement result in results { + count = count + 1; + selenium:WebElement itemPageLink = check result.findByCssSelector("h3 > a"); + check itemPageLink.click(); + runtime:sleep(1); + + selenium:WebElement itemContent = check driver.findByClassName("product_main"); + string bookName = check (check itemContent.findByTagName("h1")).getText(); + string price = check (check itemContent.findByClassName("price_color")).getText(); + string availability = check (check itemContent.findByClassName("availability")).getText(); + + io:println(string `${count}. Name: ${bookName}, Price: ${price}, Availability: ${availability}` + "\n"); + check driver.navigateBack(); + runtime:sleep(1); + } + + check driver.quit(); + +} diff --git a/gradle.properties b/gradle.properties index 9b5e365..234b878 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,14 @@ org.gradle.caching=true -group=io.ballerina.lib +group=io.xlibb version=0.1.0-SNAPSHOT +checkstylePluginVersion=10.12.0 +spotbugsPluginVersion=5.0.14 +shadowJarPluginVersion=8.1.1 +downloadPluginVersion=5.4.0 +releasePluginVersion=2.8.0 + releasePluginVersion=2.8.0 ballerinaGradlePluginVersion=3.0.0 ballerinaLangVersion=2201.10.2 +seleniumVersion=4.27.0 diff --git a/native/build.gradle b/native/build.gradle new file mode 100644 index 0000000..782824f --- /dev/null +++ b/native/build.gradle @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +plugins { + id 'java' + id 'checkstyle' + id 'com.github.spotbugs' +} + +description = 'Ballerina - Native' + +dependencies { + checkstyle project(':checkstyle') + checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}" + + implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" + implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: "${seleniumVersion}" +} + +def excludePattern = '**/module-info.java' +tasks.withType(Checkstyle) { + exclude excludePattern +} + +checkstyle { + toolVersion "${project.checkstylePluginVersion}" + configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml") + configProperties = ["suppressionFile" : file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] +} + +checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") + +spotbugsMain { + effort "max" + reportLevel "low" + reportsDir = file("$project.buildDir/reports/spotbugs") + reports { + html.enabled true + text.enabled = true + } + def excludeFile = file("${rootDir}/build-config/spotbugs-exclude.xml") + if(excludeFile.exists()) { + excludeFilter = excludeFile + } +} + +spotbugsTest { + enabled = false +} + +compileJava { + doFirst { + options.compilerArgs = [ + '--module-path', classpath.asPath, + ] + classpath = files() + } +} diff --git a/native/src/main/java/io/xlibb/selenium/SeleniumWebDriver.java b/native/src/main/java/io/xlibb/selenium/SeleniumWebDriver.java new file mode 100644 index 0000000..e458b96 --- /dev/null +++ b/native/src/main/java/io/xlibb/selenium/SeleniumWebDriver.java @@ -0,0 +1,409 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package io.xlibb.selenium; + +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BString; +import io.xlibb.selenium.utils.Utils; +import org.openqa.selenium.By; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.firefox.FirefoxDriver; +import org.openqa.selenium.firefox.FirefoxOptions; + +import java.util.List; +import java.util.Set; + +/** + * Provide APIs to interact with web browsers. + */ +public class SeleniumWebDriver { + + public static Object openChrome(BObject webDriver, BMap