main 14599d820b99 cached
159 files
66.3 MB
266.3k tokens
228 symbols
1 requests
Download .txt
Showing preview only (794K chars total). Download the full file or copy to clipboard to get everything.
Repository: appium-userland/appium-flutter-driver
Branch: main
Commit: 14599d820b99
Files: 159
Total size: 66.3 MB

Directory structure:
gitextract_00f0ctj7/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── dotnet.yml
│       ├── driver-function.yml
│       ├── finder-kotlin.yml
│       ├── finder-node.js.yml
│       ├── finder-python.yml
│       ├── finder-ruby.yml
│       ├── nodejs.yml
│       └── publish.yml
├── .gitignore
├── .vscode/
│   └── launch.json
├── LICENSE
├── README.md
├── driver/
│   ├── .eslintrc.json
│   ├── .gitignore
│   ├── .npmrc
│   ├── .releaserc
│   ├── CHANGELOG.md
│   ├── eslint.config.mjs
│   ├── lib/
│   │   ├── commands/
│   │   │   ├── assertions.ts
│   │   │   ├── clipboard.ts
│   │   │   ├── context.ts
│   │   │   ├── element.ts
│   │   │   ├── execute/
│   │   │   │   ├── scroll.ts
│   │   │   │   └── wait.ts
│   │   │   ├── execute.ts
│   │   │   ├── gesture.ts
│   │   │   └── screen.ts
│   │   ├── desired-caps.ts
│   │   ├── doctor/
│   │   │   └── checks.js
│   │   ├── driver.ts
│   │   ├── ios/
│   │   │   └── app.ts
│   │   ├── logger.ts
│   │   ├── platform.ts
│   │   └── sessions/
│   │       ├── android.ts
│   │       ├── base64url.ts
│   │       ├── ios.ts
│   │       ├── isolate_socket.ts
│   │       ├── log-monitor.ts
│   │       ├── observatory.ts
│   │       └── session.ts
│   ├── npm-shrinkwrap.json
│   ├── package.json
│   ├── release.sh
│   ├── scripts/
│   │   └── download-wda-sim.mjs
│   └── tsconfig.json
├── example/
│   ├── .gitignore
│   ├── dart/
│   │   ├── drag_commands.dart
│   │   └── get_text_command.dart
│   ├── flutter_app_under_test/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── android/
│   │   │   ├── app/
│   │   │   │   ├── build.gradle
│   │   │   │   └── src/
│   │   │   │       ├── debug/
│   │   │   │       │   └── AndroidManifest.xml
│   │   │   │       ├── main/
│   │   │   │       │   ├── AndroidManifest.xml
│   │   │   │       │   ├── java/
│   │   │   │       │   │   └── com/
│   │   │   │       │   │       └── example/
│   │   │   │       │   │           └── hello_world/
│   │   │   │       │   │               └── MainActivity.java
│   │   │   │       │   └── res/
│   │   │   │       │       ├── drawable/
│   │   │   │       │       │   └── launch_background.xml
│   │   │   │       │       └── values/
│   │   │   │       │           └── styles.xml
│   │   │   │       └── profile/
│   │   │   │           └── AndroidManifest.xml
│   │   │   ├── build.gradle
│   │   │   ├── gradle/
│   │   │   │   └── wrapper/
│   │   │   │       └── gradle-wrapper.properties
│   │   │   ├── gradle.properties
│   │   │   └── settings.gradle
│   │   ├── ios/
│   │   │   ├── Flutter/
│   │   │   │   ├── AppFrameworkInfo.plist
│   │   │   │   ├── Debug.xcconfig
│   │   │   │   └── Release.xcconfig
│   │   │   ├── Runner/
│   │   │   │   ├── AppDelegate.h
│   │   │   │   ├── AppDelegate.m
│   │   │   │   ├── Assets.xcassets/
│   │   │   │   │   ├── AppIcon.appiconset/
│   │   │   │   │   │   └── Contents.json
│   │   │   │   │   └── LaunchImage.imageset/
│   │   │   │   │       ├── Contents.json
│   │   │   │   │       └── README.md
│   │   │   │   ├── Base.lproj/
│   │   │   │   │   ├── LaunchScreen.storyboard
│   │   │   │   │   └── Main.storyboard
│   │   │   │   ├── Info.plist
│   │   │   │   └── main.m
│   │   │   ├── Runner.xcodeproj/
│   │   │   │   ├── project.pbxproj
│   │   │   │   ├── project.xcworkspace/
│   │   │   │   │   └── contents.xcworkspacedata
│   │   │   │   └── xcshareddata/
│   │   │   │       └── xcschemes/
│   │   │   │           └── Runner.xcscheme
│   │   │   └── Runner.xcworkspace/
│   │   │       └── contents.xcworkspacedata
│   │   ├── lib/
│   │   │   ├── main.dart
│   │   │   ├── main.g.dart
│   │   │   └── stream.dart
│   │   ├── pubspec.yaml
│   │   ├── test/
│   │   │   └── widget_test.dart
│   │   └── test_driver/
│   │       └── main_test.dart
│   ├── java/
│   │   ├── .gitignore
│   │   ├── .vscode/
│   │   │   └── settings.json
│   │   ├── pom.xml
│   │   └── src/
│   │       └── test/
│   │           └── java/
│   │               └── example/
│   │                   └── appium/
│   │                       ├── BaseDriver.java
│   │                       └── FlutterTest.java
│   ├── nodejs/
│   │   ├── README.md
│   │   ├── package.json
│   │   └── src/
│   │       └── index.js
│   ├── python/
│   │   ├── example.py
│   │   └── requirement.txt
│   ├── ruby/
│   │   ├── Gemfile
│   │   ├── example.rb
│   │   ├── example_sample2.rb
│   │   └── example_sample2_ios.rb
│   └── sample2/
│       ├── README.md
│       └── app-debug.apk
├── finder/
│   ├── dotnet/
│   │   ├── AppiumFlutterFinder/
│   │   │   ├── AppiumFlutterFinder.csproj
│   │   │   ├── FlutterBy.cs
│   │   │   ├── FlutterDriver.cs
│   │   │   ├── FlutterElement.cs
│   │   │   ├── FlutterElementFactory.cs
│   │   │   ├── License.md
│   │   │   └── Readme.md
│   │   ├── AppiumFlutterFinderTests/
│   │   │   ├── AppiumFlutterFinderTests.csproj
│   │   │   ├── FlutterFinderTests.cs
│   │   │   └── Usings.cs
│   │   └── DotNetAppiumFlutterFinder.sln
│   ├── kotlin/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   ├── gradle/
│   │   │   └── wrapper/
│   │   │       ├── gradle-wrapper.jar
│   │   │       └── gradle-wrapper.properties
│   │   ├── gradle.properties
│   │   ├── gradlew
│   │   ├── gradlew.bat
│   │   ├── settings.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   └── kotlin/
│   │       │       └── pro/
│   │       │           └── truongsinh/
│   │       │               └── appium_flutter/
│   │       │                   ├── FlutterFinder.kt
│   │       │                   └── finder/
│   │       │                       ├── FlutterElement.kt
│   │       │                       ├── ancestor.kt
│   │       │                       ├── bySemanticsLabel.kt
│   │       │                       ├── byTooltip.kt
│   │       │                       ├── byType.kt
│   │       │                       ├── byValueKey.kt
│   │       │                       ├── descendant.kt
│   │       │                       ├── pageback.kt
│   │       │                       ├── serializer.kt
│   │       │                       └── text.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── pro/
│   │                   └── truongsinh/
│   │                       └── appium_flutter/
│   │                           └── finder/
│   │                               └── FinderTest.kt
│   ├── nodejs/
│   │   ├── LICENSE
│   │   ├── README.md
│   │   ├── lib/
│   │   │   ├── base64url.ts
│   │   │   ├── base64url_test.ts
│   │   │   ├── deserializer.ts
│   │   │   ├── serializer.ts
│   │   │   └── serializer_test.ts
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── tslint.json
│   ├── python/
│   │   ├── .pylintrc
│   │   ├── README.md
│   │   ├── appium_flutter_finder/
│   │   │   ├── __init__.py
│   │   │   └── flutter_finder.py
│   │   ├── setup.py
│   │   └── tests/
│   │       ├── __init__.py
│   │       └── flutter_finder_tests.py
│   ├── ruby/
│   │   ├── Gemfile
│   │   ├── README.md
│   │   ├── Rakefile
│   │   ├── appium_flutter_finder.gemspec
│   │   ├── lib/
│   │   │   ├── appium_flutter_finder/
│   │   │   │   └── version.rb
│   │   │   └── appium_flutter_finder.rb
│   │   └── test/
│   │       ├── appium_flutter_finder_test.rb
│   │       └── test_helper.rb
│   └── spec.json
└── jitpack.yml

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/driver"
    schedule:
      interval: "weekly"
  - package-ecosystem: "npm"
    directory: "/example/nodejs"
    schedule:
      interval: "weekly"
  - package-ecosystem: "npm"
    directory: "/finder/nodejs"
    schedule:
      interval: "weekly"
  - package-ecosystem: "pip"
    directory: "/finder/python"
    schedule:
      interval: "weekly"
  - package-ecosystem: "bundler"
    directory: "/finder/ruby"
    schedule:
      interval: "weekly"
  - package-ecosystem: github-actions
    directory: "/"
    schedule:
      interval: weekly
      time: "11:00"
      day: "tuesday"
    open-pull-requests-limit: 5
    commit-message:
      prefix: "ci"
      include: "scope"


================================================
FILE: .github/workflows/dotnet.yml
================================================
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net

name: .NET

on:
  workflow_dispatch:
  push:
    paths:
      - 'finder/python/**'
    branches: [ main ]
  pull_request:
    paths:
      - 'finder/python/**'
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v6
    - name: Setup .NET
      uses: actions/setup-dotnet@v5
      with:
        dotnet-version: 6.0.x
    - name: Restore dependencies
      run: dotnet restore
      working-directory: finder/dotnet
    - name: Build
      run: dotnet build --no-restore
      working-directory: finder/dotnet
    - name: Run tests
      run: dotnet test --no-build --verbosity normal
      working-directory: finder/dotnet


================================================
FILE: .github/workflows/driver-function.yml
================================================
name: Run quick functionality check

on:
  workflow_dispatch:
  push:
    paths:
      - 'driver/**'
    branches: [ main ]
  pull_request:
    paths:
      - 'driver/**'
    branches: [ main ]

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  ios_test:
    runs-on: macos-15

    env:
      XCODE_VERSION: 16.4
      IOS_DEVICE_NAME: iPhone 16 Plus
      IOS_VERSION: 18.5

    steps:
    - uses: actions/checkout@v6

    - name: Install Node.js
      uses: actions/setup-node@v6
      with:
        node-version: 'lts/*'

    - name: Select Xcode
      uses: maxim-lobanov/setup-xcode@v1
      with:
        xcode-version: ${{ env.XCODE_VERSION }}
    - run: defaults write com.apple.iphonesimulator PasteboardAutomaticSync -bool false

    - uses: futureware-tech/simulator-action@v5
      with:
        # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md
        model: ${{ env.IOS_DEVICE_NAME }}
        os_version: ${{ env.IOS_VERSION }}

    # Start Appium
    - run: npm install -g appium
    - run: |
        npm install
        appium driver install --source=local ./
        nohup appium --log-timestamp --log-no-colors > appium.log &
      working-directory:
        driver

    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: 3.2
    - name: Install dependencies
      run: bundle install
      working-directory: example/ruby
    - name: Run tests
      run: ruby example_sample2_ios.rb
      working-directory: example/ruby

    - name: Save server output
      if: ${{ always() }}
      uses: actions/upload-artifact@master
      with:
        name: appium-ios.log
        path: driver/appium.log

  android_test:
    runs-on: ubuntu-latest

    env:
      API_LEVEL: 34
      ARCH: x86_64

    steps:
    - uses: actions/checkout@v6

    - uses: actions/setup-java@v5
      with:
        distribution: 'temurin'
        java-version: '17'

    - name: Install Node.js
      uses: actions/setup-node@v6
      with:
        node-version: 'lts/*'

    # Start Appium
    - run: npm install -g appium
    - run: |
        npm install
        nohup appium --log-timestamp --log-no-colors > appium.log &
      working-directory:
        driver

    - name: Enable KVM group perms
      run: |
        echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
        sudo udevadm control --reload-rules
        sudo udevadm trigger --name-match=kvm

    - name: AVD cache
      uses: actions/cache@v5
      id: avd-cache
      with:
        path: |
          ~/.android/avd/*
          ~/.android/adb*
        key: avd-${{ env.API_LEVEL }}
    - name: create AVD and generate snapshot for caching
      if: steps.avd-cache.outputs.cache-hit != 'true'
      uses: reactivecircus/android-emulator-runner@v2
      with:
        api-level: ${{ env.API_LEVEL }}
        arch: ${{ env.ARCH }}
        target: google_apis
        force-avd-creation: false
        emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
        disable-animations: false
        script: echo "Generated AVD snapshot for caching."

    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: 3.2
    - name: Install dependencies
      run: bundle install
      working-directory: example/ruby

    - name: Run tests
      uses: reactivecircus/android-emulator-runner@v2
      with:
        api-level: ${{ env.API_LEVEL }}
        arch: ${{ env.ARCH }}
        script: ruby example/ruby/example_sample2.rb
        target: google_apis
        profile: Nexus 5X
        disable-spellchecker: true
        disable-animations: true
      env:
        ANDROID_SDK_VERSION: ${{ env.API_LEVEL }}
        APPIUM_DRIVER: ${{matrix.test_targets.automation_name}}
        IGNORE_VERSION_SKIP: true
        CI: true

    - name: Save server output
      if: ${{ always() }}
      uses: actions/upload-artifact@master
      with:
        name: appium-android.log
        path: driver/appium.log


================================================
FILE: .github/workflows/finder-kotlin.yml
================================================
name: Kotlin

on:
  workflow_dispatch:
  push:
    paths:
      - 'finder/kotlin/**'
    branches: [ main ]
  pull_request:
    paths:
      - 'finder/kotlin/**'
    branches: [ main ]

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v6
    - name: Set up JDK 11
      uses: actions/setup-java@v5
      with:
        java-version: '11'
        distribution: 'temurin'
    - name: Run tests
      run: ./gradlew test
      working-directory: finder/kotlin


================================================
FILE: .github/workflows/finder-node.js.yml
================================================
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Finer for NodeJS

on:
  workflow_dispatch:
  push:
    branches: [ main ]
    paths:
      - 'finder/nodejs/**'
  pull_request:
    branches: [ main ]
    paths:
      - 'finder/nodejs/**'

jobs:
  prepare_matrix:
    runs-on: ubuntu-latest
    outputs:
      versions: ${{ steps.generate-matrix.outputs.versions }}
    steps:
    - name: Select 3 most recent LTS versions of Node.js
      id: generate-matrix
      run: echo "versions=$(curl -s https://endoflife.date/api/nodejs.json | jq -c '[[.[] | select(.lts != false)][:3] | .[].cycle | tonumber]')" >> "$GITHUB_OUTPUT"

  build:
    needs:
    - prepare_matrix

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version:  ${{ fromJSON(needs.prepare_matrix.outputs.versions) }}
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/

    steps:
    - uses: actions/checkout@v6
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v6
      with:
        node-version: ${{ matrix.node-version }}
    - run: |
        npm install
        npm run lint
        npm run test
      working-directory: finder/nodejs


================================================
FILE: .github/workflows/finder-python.yml
================================================
name: Python

on:
  workflow_dispatch:
  push:
    paths:
      - 'finder/python/**'
    branches: [ main ]
  pull_request:
    paths:
      - 'finder/python/**'
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']

    steps:
    - uses: actions/checkout@v6
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v6
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install pylint pytest Appium-Python-Client
    - name: Analyzing the code with pylint
      run: pylint tests/ appium_flutter_finder/
      working-directory: finder/python
    - name: Run tests
      run: pytest tests/**
      working-directory: finder/python


================================================
FILE: .github/workflows/finder-ruby.yml
================================================
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby

name: Ruby

on:
  workflow_dispatch:
  push:
    paths:
      - 'finder/ruby/**'
    branches: [ main ]
  pull_request:
    paths:
      - 'finder/ruby/**'
    branches: [ main ]

jobs:
  test:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        ruby-version: ['3.1', '3.2', '3.3']

    steps:
    - uses: actions/checkout@v6
    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: ${{ matrix.ruby-version }}
        bundler-cache: true
        working-directory: finder/ruby
    - name: Run tests
      run: bundle exec rake
      working-directory: finder/ruby


================================================
FILE: .github/workflows/nodejs.yml
================================================
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
  workflow_dispatch:
  push:
    paths:
      - 'driver/**'
    branches: [ main ]
  pull_request:
    paths:
      - 'driver/**'
    branches: [ main ]

jobs:
  node_matrix:
    uses: appium/appium-workflows/.github/workflows/node-lts-matrix.yml@main

  build:
    needs:
    - node_matrix

    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        node-version: ${{ fromJSON(needs.node_matrix.outputs.versions) }}
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/

    steps:
    - uses: actions/checkout@v6
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v6
      with:
        node-version: ${{ matrix.node-version }}
    - run: |
        npm install
        npm run lint
        npm run format:check
      working-directory: driver


================================================
FILE: .github/workflows/publish.yml
================================================
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages

name: publish driver

# FIXME
# npm token kep raising as invalid while it had proper one.
on:
  workflow_dispatch:
#  push:
#    branches: [ main ]
#    # paths:
#    #   - 'driver/**'

permissions:
  contents: write
  pull-requests: write
  issues: write
  id-token: write # to enable use of OIDC for trusted publishing and npm provenance

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: lts/*
          check-latest: true
      - run: npm install
        working-directory: driver
      - run: npm test
        working-directory: driver

  publish-npm:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 2
      - uses: actions/setup-node@v6
        with:
          node-version: lts/*
          check-latest: true
      - run: npm install
        working-directory: driver
      - run: npm publish
        name: Publish the package
        working-directory: driver


================================================
FILE: .gitignore
================================================
.DS_Store

# Logs
logs
*.log
npm-debug.log*
package-lock.json*

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules
jspm_packages

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

# Ignore gulped JS
build

# Pack output
*.tgz

# python
finder/python/build
finder/python/dist
finder/python/Appium_Flutter_Finder.egg-info

#dotnet
.vs/
bin/
obj/


================================================
FILE: .vscode/launch.json
================================================
{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Run test iOS",
      "env": {
        "APPIUM_OS": "ios"
      },
      "program": "${workspaceFolder}/example/src/index.js",
      "outFiles": [
        "${workspaceFolder}/**/*.js"
      ]
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Run test Android",
      "env": {
        "APPIUM_OS": "android"
      },
      "program": "${workspaceFolder}/example/src/index.js",
      "outFiles": [
        "${workspaceFolder}/**/*.js"
      ]
    }
  ]
}

================================================
FILE: LICENSE
================================================
MIT License
-----------

Copyright (c) 2019 TruongSinh Tran-Nguyen <i@truongsinh.pro>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# Appium Flutter Driver

[![NPM version](https://img.shields.io/npm/v/appium-flutter-driver.svg)](https://npmjs.org/package/appium-flutter-driver)
[![Downloads](https://img.shields.io/npm/dm/appium-flutter-driver.svg)](https://npmjs.org/package/appium-flutter-driver)

Appium Flutter Driver is a test automation tool for [Flutter](https://flutter.dev) apps on multiple platforms/OSes. Appium Flutter Driver is part of the [Appium](https://github.com/appium/appium) mobile test automation tool maintained by the community. Feel free to create PRs to fix issues/improve this driver.

## Flutter Driver vs Appium Flutter Driver
Even though Flutter comes with superb integration test support, [Flutter Driver](https://flutter.dev/docs/cookbook/testing/integration/introduction), it does not fit some specific use cases, such as
- Writing tests in other languages than Dart
- Running integration test for Flutter app with embedded webview or native view, or existing native app with embedded Flutter view
- Running tests on multiple devices simultaneously
- Running integration tests on device farms that offer Appium support (Please contact the availability for each vendor)

Under the hood, Appium Flutter Driver uses the [Dart VM Service Protocol](https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md) with extension `ext.flutter.driver`, similar to Flutter Driver, to control the Flutter app-under-test (AUT).

## Appium drivers for Flutter

Appium community currently has two drivers for Flutter environment:

- Appium Flutter driver (this driver)
    - Run Flutter commands over websocket connection against the observaory URL (calls Flutter APIs directly)
    - Base APIs are [`flutter_driver`](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html)
- [`Appium Flutter Integration Driver`](https://github.com/AppiumTestDistribution/appium-flutter-integration-driver)
    - Run a server on DartVM as part of the application under test in order to call Flutter APIs via the server
    - Base APIs are [`integration_test`](https://github.com/flutter/flutter/tree/main/packages/integration_test#integration_test)

## Appium Flutter Driver or Appium UiAutomator2/XCUITest driver

As a baseline, we recommend using [official testing tools and strategy](https://docs.flutter.dev/testing/overview) to test Flutter apps as possible in Dart.

If you'd like to test a release app, which can be released from app store as-is, Appium [UIAutomator2](https://github.com/appium/appium-uiautomator2-driver)/[XCUITest](https://github.com/appium/appium-xcuitest-driver) driver is a good choice. Since Flutter 3.19, Flutter apps can expose [`identifier` for `SemanticsProperties`](https://api.flutter.dev/flutter/semantics/SemanticsProperties/identifier.html) as `resource-id` in Android and `accessibilityIdentifier` in iOS. They should help to achieve automation against release apps with Appium [UIAutomator2](https://github.com/appium/appium-uiautomator2-driver)/[XCUITest](https://github.com/appium/appium-xcuitest-driver) as blackbox testing.

- Appium Flutter driver has three contexts to manage the application under test and the device under test. To achieve the `FLUTTER` context, the test package requires testing tools to import. The application under test cannot release as-is.
    - `FLUTTER` context sends commands to the Dart VM directly over the observatory URL. This allows you to interact with Flutter elements directly.
    - `NATIVE_APP` context is the same as the regular Appium [UIAutomator2](https://github.com/appium/appium-uiautomator2-driver)/[XCUITest](https://github.com/appium/appium-xcuitest-driver) driver
    - `WEBVIEW` context manages the WebView contents over Appium UiAutomator2/XCUITest driver
- (**Recommended** if possible) Appium [UIAutomator2](https://github.com/appium/appium-uiautomator2-driver)/[XCUITest](https://github.com/appium/appium-xcuitest-driver) driver directly must be sufficient to achieve automation if the application under test had `semanticLabel` properly. Then, the accessibility mechanism in each OS can expose elements for Appium through OS's accessibility features.
    - In addition to `semanticLabel`, Flutter 3.19+ may have [`identifier` for `SemanticsProperties`](https://api.flutter.dev/flutter/semantics/SemanticsProperties/identifier.html) (introduced by https://github.com/flutter/flutter/pull/138331). It sets `resource-id` and `accessibilityIdentifier` for Android and iOS, then UiAutomator2/XCUITest drivers might also be able to interact with these elements without Appium Flutter Driver.
        - `"appium:settings[disableIdLocatorAutocompletion]": true` or configuring `disableIdLocatorAutocompletion` via [Settings API](https://appium.io/docs/en/latest/guides/settings/) would be necessary to make `resource-id` idea work without any package name prefix like Android compose.
        - e.g. https://github.com/flutter/flutter/issues/17988#issuecomment-1867097631

## Installation

Appium Flutter Driver version `3.0.0` requires Appium 3.

```
appium driver install --source=npm appium-flutter-driver
```

As a local:

```
appium driver install --source local /path/to/appium-flutter-driver/driver
```

## Usage and requirement
If you are unfamiliar with running Appium tests, start with [Quickstart Intro](https://appium.io/docs/en/latest/quickstart/) first.

Your Flutter application must be compiled in `debug` or `profile` mode. The dependency must have **[`flutter_driver`](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html)** package like the below `pubspec.yaml` example with [`enableFlutterDriverExtension`](https://api.flutter.dev/flutter/flutter_driver_extension/flutter_driver_extension-library.html) configuration in the `main.dart`.

```yaml
# pubspec.yaml
dev_dependencies:
  flutter_driver:
    sdk: flutter
```

This snippet, taken from [example directory](example), is a script written as an appium client with `webdriverio`, and assumes you have `appium` server (with `appium-flutter-driver` installed) running on the same host and default port (`4723`). For more info, see example's [README.md](https://github.com/appium/appium-flutter-driver/tree/main/example/nodejs/README.md)

> **Note**
>
> This means this driver depends on [`flutter_driver`](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html).
> In the past, the Flutter team announced replacing `flutter_driver` with `integration_test`, but according to [this ticket](https://github.com/flutter/flutter/issues/148028), this discussion is still ongoing.
> So flutter_driver would continue to be maintained for now.

Each client needs [each finder](finder) module to handle [Finders](#Finders). Appium Flutter Driver communicates with the Dart VM directory in the `FLUTTER` context.

### Doctor
Since driver version 2.4.0 you can automate the validation for the most of the above requirements as well as various optional ones needed by driver extensions by running the `appium driver doctor flutter` server command.
The check runs for Android for UIAutomator2 driver and iOS for XCUITest driver.

`SKIP_ANDROID` or `SKIP_IOS` environment variable helps to skip these checks.

```
# skip Android check
SKIP_ANDROID=1 appium driver doctor flutter
# skip iOS check
SKIP_IOS=1 appium driver doctor flutter
```

### Note
- Flutter context does not support page source
    - Please use `getRenderTree` command instead
- You can send appium-xcuitest-driver/appium-uiautomator2-driver commands in `NATIVE_APP` context
- `scrollUntilVisible` command : An expectation for checking that an element, known to be present on the widget tree, is visible. Using waitFor to wait element
- `scrollUntilTapable` command : An expectation for checking an element is visible and enabled such that you can click it. Using waitTapable to wait element
- `driver.activateApp(appId)` starts the given app and attaches to the observatory URL in the `FLUTTER` context. The method may raise an exception if no observaotry URL was found. The typical case is the `appId` is already running. Then, the driver will fail to find the observatory URL.
- `getClipboard` and `setClipboard` depend on each `NATIVE_APP` context behavior
- Launch via `flutter:launchApp` or 3rd party tool (via instrument service) and attach to the Dart VM for an iOS real device (profile build)
    1. Do not set `app` nor `bundleId` to start a session without launching apps
    2. Start the app process via 3rd party tools such as [go-ios](https://github.com/danielpaulus/go-ios) to start the app process with debug mode in the middle of the new session process in 1) the above.
          - Then, the appium flutter session establish the WebSocket and proceed the session
- keyboard interaction may not work in Android because of https://github.com/flutter/flutter/issues/15415 that is caused by [`flutter_driver`](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html)

## Capabilities

### For the Appium Flutter Driver only

| Capability | Description | Example Values |
| - | - | -|
| appium:retryBackoffTime | The interval to find the observetory url from logs. (default 3000ms)|500|
| appium:maxRetryCount    | The count to find the observatory url. (default 10)          | 20|
| appium:observatoryWsUri | The URL to attach to the Dart VM. The Appium Flutter Driver finds the WebSocket URL from the device log by default. You can skip the finding the URL process by specifying this capability. Then, this driver attempt to establish a WebSocket connection against the given WebSocket URL. Note that this capability expects the URL is ready for access by outside an appium session. This flutter driver does not do port-forwarding with this capability. You may need to coordinate the port-forwarding as well. | 'ws://127.0.0.1:60992/aaaaaaaaaaa=/ws' |
| appium:isolateId | The isolate id to attach to as the initial attempt. A session can change the isolate with `flutter:setIsolateId` command. The default behavior finds `main` isolate id and attaches it. | `isolates/2978358234363215`, `2978358234363215` |
| appium:skipPortForward | Whether skip port forwarding from the flutter driver local to the device under test with `observatoryWsUri` capability. It helps you to manage the application under test, the observatory URL and the port forwarding configuration. The default is `true`. | true, false |
| appium:remoteAdbHost | The IP/hostname of the remote host ADB is running on. This capability only makes sense for Android platform. Providing it will implicitly override the host for the Observatory URL if the latter is determined from device logs. localhost be default | 192.168.1.20
| appium:adbPort | The port number ADB server is running on. This capability only makes sense for Android platform. 5037 by default | 9999
| appium:forwardingPort | The port number that will be used to forward the traffic from the device under test to localhost. Only applicable if `skipPortForward` is falsy. Not applicable if the test is executed on iOS Simulator. By default, it is the same as in the provided or autodetected Observatory URL. | 9999

### UIA2/XCUITest driver

Please check each driver's documentation
- https://github.com/appium/appium-uiautomator2-driver
- https://appium.github.io/appium-xcuitest-driver/latest/capabilities/

## Context Management

Appium Flutter Driver allows you to send [`flutter_driver`](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html) commands to the Dart VM in the `FLUTTER` context, but it does not support native Android/iOS since the Dart VM can handle in the Dart VM contents. `NATIVE_APP` context provides you to use the UIA2 driver for Android and the XCUITest driver for iOS automation. `WEBVIEW_XXXX` context helps WebView testing over the UIA2/XCUITest driver that is not available via the flutter_driver.

Thus, you need to switch proper contexts, `FLUTTER`, `NATIVE_APP` or `WEBVIEW_XXXX`, to automate a proper application target.

### Example

```js
# webdriverio
const wdio = require('webdriverio');
const assert = require('assert');
const { byValueKey } = require('appium-flutter-finder');

const osSpecificOps = process.env.APPIUM_OS === 'android' ? {
  'platformName': 'Android',
  'appium:deviceName': 'Pixel 2',
  'appium:app': __dirname +  '/../apps/app-free-debug.apk',
}: process.env.APPIUM_OS === 'ios' ? {
  'platformName': 'iOS',
  'appium:platformVersion': '12.2',
  'appium:deviceName': 'iPhone X',
  'appium:noReset': true,
  'appium:app': __dirname +  '/../apps/Runner.zip',
} : {};

const opts = {
  port: 4723,
  capabilities: {
    ...osSpecificOps,
    'appium:automationName': 'Flutter',
    'appium:retryBackoffTime': 500
  }
};

(async () => {
  const counterTextFinder = byValueKey('counter');
  const buttonFinder = byValueKey('increment');

  const driver = await wdio.remote(opts);

  if (process.env.APPIUM_OS === 'android') {
    await driver.switchContext('NATIVE_APP');
    await (await driver.$('~fab')).click();
    await driver.switchContext('FLUTTER');
  } else {
    console.log('Switching context to `NATIVE_APP` is currently only applicable to Android demo app.')
  }

  assert.strictEqual(await driver.getElementText(counterTextFinder), '0');

  await driver.elementClick(buttonFinder);
  await driver.touchAction({
    action: 'tap',
    element: { elementId: buttonFinder }
  });

  assert.strictEqual(await driver.getElementText(counterTextFinder), '2');

  driver.deleteSession();
})();
```

Please check [example](example) in this repository for more languages.

## Several ways to start an application

You have a couple of methods to start the application under test by establishing the Dart VM connection as below:

1. Start with `app` in the capabilities
    1. The most standard method. You may need to start a new session with `app` capability. Then, appium-flutter-driver will start the app, and establish a connection with the Dart VM immediately.
2. Start with `activate_app`: for users who want to start the application under test in the middle of a session
    1. Start a session without `app` capability
    2. Install the application under test via `driver.install_app` or `mobile:installApp` command
    3. Activate the app via `driver.activate_app` or `mobile:activateApp` command
        - Then, appium-flutter-driver establish a connection with the Dart VM
3. Launch the app outside the driver: for users who want to manage the application under test by yourselves
    1. Start a session without `app` capability
    2. Install the application under test via `driver.install_app` or `mobile:installApp` command etc
    3. Calls `flutter:connectObservatoryWsUrl` command to keep finding an observatory URL to the Dart VM
    4. (at the same time) Launch the application under test via outside the appium-flutter-driver
        - e.g. Launch an iOS process via [ios-go](https://github.com/danielpaulus/go-ios), [iproxy](https://github.com/libimobiledevice/libusbmuxd#iproxy) or [tidevice](https://github.com/alibaba/taobao-iphone-device)
    5. Once `flutter:connectObservatoryWsUrl` identify the observatory URL, the command will establish a connection to the Dart VM
4. Launch the app with `flutter:launchApp` for iOS and attach to the Dart VM: for users whom application under test do not print the observatory url via regular launch/activate app method
    1. Start a session without `app` capability
    2. Install the application under test via `driver.install_app` or `mobile:installApp` command etc
    3. Calls `flutter:launchApp` command to start an iOS app via instrument service
        - `driver.execute_script 'flutter:launchApp', 'com.example.bundleId', {arguments: ['arg1'], environment: {ENV1: 'env'}}` is example usage
        - This launching method is the same as the above 3rd party method, but does the same thing only via the appium flutter driver.

Please make sure the target app process stops before starting the target app with the above.

## Changelog

- [appium-flutter-driver](driver/CHANGELOG.md)
- [each finder](finder)


## Commands for NATIVE_APP/WEBVIEW context

Please check each driver's documentation
- https://github.com/appium/appium-uiautomator2-driver
- https://appium.github.io/appium-xcuitest-driver/latest

## Commands for FLUTTER context

Legend:

| Icon | Description |
| - | - |
| :white_check_mark: | integrated to CI |
| :ok: | manual tested without CI |
| :warning: | available without manual tested |
| :x: | unavailable |

### Finders

| Flutter Driver API | Status | WebDriver example |
| - | - | - |
| [ancestor](https://api.flutter.dev/flutter/flutter_driver/CommonFinders/ancestor.html) | :ok: |  |
| [bySemanticsLabel](https://api.flutter.dev/flutter/flutter_driver/CommonFinders/bySemanticsLabel.html) | :ok: |  |
| [byTooltip](https://api.flutter.dev/flutter/flutter_driver/CommonFinders/byTooltip.html) | :ok: | `byTooltip('Increment')` |
| [byType](https://api.flutter.dev/flutter/flutter_driver/CommonFinders/byType.html) | :ok: | `byType('TextField')` |
| [byValueKey](https://api.flutter.dev/flutter/flutter_driver/CommonFinders/byValueKey.html) | :ok: | [`byValueKey('counter')`](https://github.com/appium/appium-flutter-driver/blob/5df7386b59bb99008cb4cff262552c7259bb2af2/example/src/index.js#L30) |
| [descendant](https://api.flutter.dev/flutter/flutter_driver/CommonFinders/descendant.html) | :ok: |  |
| [pageBack](https://api.flutter.dev/flutter/flutter_driver/CommonFinders/pageBack.html) | :ok: | `pageBack()` |
| [text](https://api.flutter.dev/flutter/flutter_driver/CommonFinders/text.html) | :ok: | `byText('foo')` |

### Commands

The below _WebDriver example_ is by webdriverio.
`flutter:` prefix commands are [`mobile:` command in appium for Android and iOS](https://appium.io/docs/en/latest/guides/execute-methods/).
Please replace them properly with your client.

| Flutter API                                                                                                                        | Status | WebDriver example (JavaScript, webdriverio) | Scope             |
|------------------------------------------------------------------------------------------------------------------------------------| - | - |-------------------|
| [FlutterDriver.connectedTo](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/FlutterDriver.connectedTo.html)           | :ok: | [`wdio.remote(opts)`](https://github.com/appium/appium-flutter-driver/blob/5df7386b59bb99008cb4cff262552c7259bb2af2/example/src/index.js#L33) | Session           |
| [checkHealth](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/checkHealth.html)                                       | :ok: | `driver.execute('flutter:checkHealth')` | Session           |
| clearTextbox                                                                                                                       | :ok: | `driver.elementClear(find.byType('TextField'))` | Session           |
| [clearTimeline](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/clearTimeline.html)                                   | :ok: | `driver.execute('flutter:clearTimeline')` | Session           |
| [enterText](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/enterText.html)                                           | :ok: | `driver.elementSendKeys(find.byType('TextField'), 'I can enter text')` (no focus required) <br/> `driver.elementClick(find.byType('TextField')); driver.execute('flutter:enterText', 'I can enter text')` (focus required by tap/click first) | Session           |
| [forceGC](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/forceGC.html)                                               | :ok: | `driver.execute('flutter:forceGC')` | Session           |
| [getBottomLeft](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/getBottomLeft.html)                                   | :ok: | `driver.execute('flutter:getBottomLeft', buttonFinder)` | Widget            |
| [getBottomRight](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/getBottomRight.html)                                 | :ok: | `driver.execute('flutter:getBottomRight', buttonFinder)` | Widget            |
| [getCenter](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/getCenter.html)                                           | :ok: | `driver.execute('flutter:getCenter', buttonFinder)` | Widget            |
| [getRenderObjectDiagnostics](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/getRenderObjectDiagnostics.html)         | :ok: | `driver.execute('flutter:getRenderObjectDiagnostics', counterTextFinder, { includeProperties: true, subtreeDepth: 2 })` | Widget            |
| [getWidgetDiagnostics](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/getWidgetDiagnostics.html)                     | :ok: (v2.8.0+) | `driver.execute('flutter:getWidgetDiagnostics', counterTextFinder, { includeProperties: true, subtreeDepth: 2 })` | Widget            |
| [getRenderTree](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/getRenderTree.html)                                   | :ok: | `driver.execute('flutter: getRenderTree')` | Session           |
| [getSemanticsId](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/getSemanticsId.html)                                 | :ok: | `driver.execute('flutter:getSemanticsId', counterTextFinder)` | Widget            |
| [getText](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/getText.html)                                               | :ok: | [`driver.getElementText(counterTextFinder)`](https://github.com/appium/appium-flutter-driver/blob/5df7386b59bb99008cb4cff262552c7259bb2af2/example/src/index.js#L44) | Widget            |
| [getTopLeft](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/getTopLeft.html)                                         | :ok: | `driver.execute('flutter:getTopLeft', buttonFinder)` | Widget            |
| [getTopRight](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/getTopRight.html)                                       | :ok: | `driver.execute('flutter:getTopRight', buttonFinder)` | Widget            |
| [getVmFlags](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/getVmFlags.html)                                         | :x: |  | Session           |
| [requestData](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/requestData.html)                                       | :ok: | `driver.execute('flutter:requestData', json.dumps({"deepLink": "myapp://item/id1"}))`  | Session           |
| [runUnsynchronized](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/runUnsynchronized.html)                           | :x: |  | Session           |
| [setFrameSync](https://api.flutter.dev/flutter/flutter_driver/SetFrameSync-class.html)                                             |:ok:| `driver.execute('flutter:setFrameSync', bool , durationMilliseconds)`| Session           |
| [screenshot](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/screenshot.html)                                         | :ok: | `driver.takeScreenshot()` | Session           |
| [screenshot](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/screenshot.html)                                         | :ok: | `driver.saveScreenshot('a.png')` | Session           |
| [scroll](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/scroll.html)                                                 | :ok: | `driver.execute('flutter:scroll', find.byType('ListView'), {dx: 50, dy: -100, durationMilliseconds: 200, frequency: 30})` | Widget            |
| [scrollIntoView](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/scrollIntoView.html)                                 | :ok: | `driver.execute('flutter:scrollIntoView', find.byType('TextField'), {alignment: 0.1})` <br/> `driver.execute('flutter:scrollIntoView', find.byType('TextField'), {alignment: 0.1, timeout: 30000})` | Widget            |
| [scrollUntilVisible](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/scrollUntilVisible.html)                         | :ok: | `driver.execute('flutter:scrollUntilVisible', find.byType('ListView'), {item:find.byType('TextField'), dxScroll: 90, dyScroll: -400});`, `driver.execute('flutter:scrollUntilVisible', find.byType('ListView'), {item:find.byType('TextField'), dxScroll: 90, dyScroll: -400, waitTimeoutMilliseconds: 20000});` | Widget            |
| [scrollUntilTapable](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/scrollUntilVisible.html)                         | :ok: | `driver.execute('flutter:scrollUntilTapable', find.byType('ListView'), {item:find.byType('TextField'), dxScroll: 90, dyScroll: -400});`, `driver.execute('flutter:scrollUntilTapable', find.byType('ListView'), {item:find.byType('TextField'), dxScroll: 90, dyScroll: -400, waitTimeoutMilliseconds: 20000});` | Widget            |
| [setSemantics](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/setSemantics.html)                                     | :x: |  | Session           |
| [setTextEntryEmulation](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/setTextEntryEmulation.html)                   | :ok: | `driver.execute('flutter:setTextEntryEmulation', false)` | Session           |
| [startTracing](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/startTracing.html)                                     | :x: |  | Session           |
| [stopTracingAndDownloadTimeline](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/stopTracingAndDownloadTimeline.html) | :x: |  | Session           |
| [tap](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/tap.html)                                                       | :ok: | [`driver.elementClick(buttonFinder)`](https://github.com/appium/appium-flutter-driver/blob/5df7386b59bb99008cb4cff262552c7259bb2af2/example/src/index.js#L46) | Widget            |
| [tap](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/tap.html)                                                       | :ok: | [`driver.touchAction({action: 'tap', element: {elementId: buttonFinder}})`](https://github.com/appium/appium-flutter-driver/blob/5df7386b59bb99008cb4cff262552c7259bb2af2/example/src/index.js#L47) | Widget            |
| [tap](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/tap.html)                                                       | :ok: | [`driver.execute('flutter:clickElement', buttonFinder, {timeout:5000})`](https://github.com/appium/appium-flutter-driver/blob/5df7386b59bb99008cb4cff262552c7259bb2af2/example/src/index.js#L47) | Widget            |
| [traceAction](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/traceAction.html)                                       | :x: |  | Session           |
| [waitFor](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/waitFor.html)                                               | :ok: | `driver.execute('flutter:waitFor', buttonFinder, 100)` | Widget            |
| [waitForAbsent](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/waitForAbsent.html)                                   | :ok: | `driver.execute('flutter:waitForAbsent', buttonFinder)` | Widget            |
| [waitForTappable](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/waitForTappable.html)                               | :ok: | `driver.execute('flutter:waitForTappable', buttonFinder)` | Widget            |
| [waitUntilNoTransientCallbacks](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/waitUntilNoTransientCallbacks.html)   | :x: |  | Widget            |
| -                                                                                                                                  | :ok: | `driver.execute('flutter:getVMInfo')` | System            |
| -                                                                                                                                  | :ok: | `driver.execute('flutter:setIsolateId', 'isolates/2978358234363215')` | System            |
| -                                                                                                                                  | :ok: | `driver.execute('flutter:getIsolate', 'isolates/2978358234363215')` or `driver.execute('flutter:getIsolate')` | System            |
| :question:                                                                                                                         | :ok: | `driver.execute('flutter:longTap', find.byValueKey('increment'), {durationMilliseconds: 10000, frequency: 30})` | Widget            |
| :question:                                                                                                                         | :ok: | `driver.execute('flutter:waitForFirstFrame')` | Widget            |
| -                                                                                                                                  | :ok: | (Ruby) `driver.execute_script 'flutter:connectObservatoryWsUrl'` | Flutter Driver    |
| -                                                                                                                                  | :ok: | (Ruby) `driver.execute_script 'flutter:launchApp', 'bundleId', {arguments: ['arg1'], environment: {ENV1: 'env'}}` | Flutter Driver    |
| dragAndDropWithCommandExtension                                                                                                    | :ok: | (Python) `driver.execute_script('flutter:dragAndDropWithCommandExtension', payload)` | Command Extension |

# Flutter Visibility Assertions for Appium

This module extends the `appium-flutter-driver` with custom visibility-related commands using `driver.execute()`.

## ✅ Supported Shortcut Commands

| **Command**         | **Status** | **Example Usage**                                                                                                         | **Target** |
|---------------------|------------|--------------------------------------------------------------------------------------------------------------------------|------------|
| `assertVisible`     | ✅         | `driver.execute('flutter:assertVisible', { key: 'myKey' })`<br>`driver.execute('flutter:assertVisible', { text: 'Login' })` | Widget     |
| `assertNotVisible`  | ✅         | `driver.execute('flutter:assertNotVisible', { key: 'hiddenWidget' })`                                                   | Widget     |
| `assertTappable`    | ✅         | `driver.execute('flutter:assertTappable', { label: 'Submit' })`                                                          | Widget     |

## 🔍 Input Formats

Each assertion supports the following input formats:

- `{ key: 'valueKey' }`
- `{ text: 'Text on widget' }`
- `{ label: 'Tooltip text' }`

These map to Flutter finders:
- `byValueKey`
- `byText`
- `byTooltip`

## 📦 Integration

These commands are typically invoked using a client helper method like:

```ts
// TypeScript
await assertVisible(driver, { key: 'submit_button' });
```

Or as a flutter command.

```ruby
# ruby
driver.execute_script 'flutter:assertVisible', {text: 'Tap me!'}, 10000

# This is equivalent to
text_finder = by_text 'Tap me!'
driver.execute_script 'flutter:assertVisible', text_finder, 10000
# or
driver.execute_script 'flutter:waitFor', text_finder, 10000
```

**NOTE**
>`flutter:launchApp` launches an app via instrument service. `mobile:activateApp` and `driver.activate_app` are via XCTest API. They are a bit different.


### `isolate` handling
#### Change the flutter engine attach to

1. Get available isolate ids
    - `id` key in the value of `isolates` by `flutter:getVMInfo`
2. Set the id via `setIsolateId`

```ruby
# ruby
info = driver.execute_script 'flutter:getVMInfo'
# Change the target engine to "info['isolates'][0]['id']"
driver.execute_script 'flutter:setIsolateId', info['isolates'][0]['id']
```

#### Check current isolate, or a particular isolate

1. Get available isolates
    - `driver.execute('flutter:getVMInfo').isolates` (JS)
2. Get a particular isolate or current isolate
    - Current isolate: `driver.execute('flutter:getIsolate')` (JS)
    - Particular isolate: `driver.execute('flutter:getIsolate', 'isolates/2978358234363215')` (JS)

## Commands across contexts

These Appium commands can work across context

- `deleteSession`
- `setContext`
- `getCurrentContext`
- `getContexts`
- `activateApp('appId')`/`mobile:activateApp`
    - `mobile:activateApp` has `skipAttachObservatoryUrl` key to not try to attach to an observatory url. e.g. `driver.execute_script 'mobile:activateApp', {skipAttachObservatoryUrl: true, appId: 'com.android.chrome'}`
- `terminateApp('appId')`/`mobile:terminateApp`
- `installApp(appPath, options)`
- `getClipboard`
- `setClipboard`

## Command Extension (Flutter Driver)

This is a command extension for Flutter Driver, utilizing the [CommandExtension-class](https://api.flutter.dev/flutter/flutter_driver_extension/CommandExtension-class.html) within `ext.flutter.driver`

Available commands:

- `dragAndDropWithCommandExtension` – performs a drag-and-drop action on the screen by specifying the start and end coordinates and the action duration.
- `getTextWithCommandExtension` - get text data from Text widget that contains TextSpan widgets.

### How to use

Copy the sample dart files to the `lib` folder of your project. Please note that you don't need to copy all files, just copy the file matched with the command you need.
- dragAndDropWithCommandExtension: [drag_commands.dart](./example/dart/drag_commands.dart)
- getTextWithCommandExtension: [get_text_command.dart](./example/dart/get_text_command.dart)

The entry point must include the `List<CommandExtension>?` commands argument in either `main.dart` or `test_main.dart` to properly handle the command extension.


```dart
import 'drag_commands.dart';
import 'get_text_command.dart';

void main() {
  enableFlutterDriverExtension(
      commands: [DragCommandExtension(), GetTextCommandExtension()]);
  runApp(const MyApp());
}
```

#### Simple examples in Python

```python
# Extended commands: flutter:dragAndDropWithCommandExtension
coord_item_1 = driver.execute_script("flutter:getCenter", item_1)
coord_item_2 = driver.execute_script("flutter:getCenter", item_2)
start_x = coord_item_1["dx"]
start_y = coord_item_1["dy"]
end_y = coord_item_2["dy"]

payload = {
    "startX": start_x,
    "startY": start_y,
    "endX": "0",
    "endY": end_y,
    "duration": "15000" # minimum 15000ms needed to drag n drop
}

driver.execute_script("flutter:dragAndDropWithCommandExtension", payload)

# Extended commands: flutter:getTextWithCommandExtension
text_finder = finder.by_value_key('amount')
get_text_payload = {
    'findBy': text_finder,
}
result = driver.execute_script('flutter:getTextWithCommandExtension', payload)
print(result)
```

#### Simple examples in nodejs

```typescript
// Extended commands: flutter:dragAndDropWithCommandExtension
const payload = {
  "startX": "100",
  "startY": "100",
  "endX": "100",
  "endY": "600",
  "duration": "15000"
}
const result = await driver.execute("flutter:dragAndDropWithCommandExtension", payload);
console.log(JSON.stringify(result));

// Extended commands: flutter:getTextWithCommandExtension
import {byValueKey} from "appium-flutter-finder";
const payload = {
    'findBy': byValueKey('amount'),
  };
const getTextResult = await driver.execute('flutter:getTextWithCommandExtension', payload);
console.log(JSON.stringify(getTextResult));

```

For debugging or testing in other programming languages, you can use the APK available in this [repository](https://github.com/Alpaca00/command-driven-list) or build an IPA.


## Troubleshooting

- Input texts https://github.com/appium/appium-flutter-driver/issues/417
- Looks hanging in `click` https://github.com/appium/appium-flutter-driver/issues/181#issuecomment-1323684510
    - `flutter:setFrameSync` may help
- `flutter:waitFor` would help to handle "an element does not exist/is not enabled" behavior. [example issue](https://github.com/appium/appium-flutter-driver/issues/693)
- Appium Inspector does not work with FLUTTER context
- `enableFlutterDriverExtension()` must be called before calling `WidgetsFlutterBinding.ensureInitialized` to avoid `Binding is already initialized to WidgetsFlutterBinding` error which could cause `"ext.flutter.driver" is not found in "extensionRPCs"` error. [issue](https://github.com/appium/appium-flutter-driver/issues/756)

## TODO?

- [ ] switching context between Flutter and [AndroidView](https://api.flutter.dev/flutter/widgets/AndroidView-class.html)
- [ ] switching context between Flutter and [UiKitView](https://api.flutter.dev/flutter/widgets/UiKitView-class.html)
- [ ] Web: `FLUTTER_WEB` context?
- [ ] macOS: with https://github.com/appium/appium-mac2-driver
- [ ] Windws?
- [ ] Linux?

## Release appium-flutter-driver

```
$ cd driver
$ sh release.sh
$ npm version <major|minor|patch>
# update changelog
$ git commit -am 'chore: bump version'
$ git tag <version number> # e.g. git tag v0.0.32
$ git push origin v0.0.32
$ git push origin main
$ npm publish
```

### Java implementation

- https://github.com/appium/appium-flutter-driver/tree/main/finder/kotlin via jitpack
- https://github.com/ashwithpoojary98/javaflutterfinder
- https://github.com/5v1988/appium-flutter-client


================================================
FILE: driver/.eslintrc.json
================================================
{
  "extends": ["@appium/eslint-config-appium-ts"]
}


================================================
FILE: driver/.gitignore
================================================
# copy from parent dir
LICENSE
README.md


================================================
FILE: driver/.npmrc
================================================
package-lock=false

================================================
FILE: driver/.releaserc
================================================

{
  "plugins": [
    ["@semantic-release/commit-analyzer", {
      "preset": "angular",
      "releaseRules": [
        {"type": "chore", "release": "patch"}
      ]
    }],
    ["@semantic-release/release-notes-generator", {
      "preset": "conventionalcommits",
      "presetConfig": {
        "types": [
          {"type": "feat", "section": "Features"},
          {"type": "fix", "section": "Bug Fixes"},
          {"type": "perf", "section": "Performance Improvements"},
          {"type": "revert", "section": "Reverts"},
          {"type": "chore", "section": "Miscellaneous Chores", "hidden": true},
          {"type": "refactor", "section": "Code Refactoring"},
          {"type": "docs", "section": "Documentation", "hidden": true},
          {"type": "style", "section": "Styles", "hidden": true},
          {"type": "test", "section": "Tests", "hidden": true},
          {"type": "build", "section": "Build System", "hidden": true},
          {"type": "ci", "section": "Continuous Integration", "hidden": true}
        ]
      }
    }],
    ["@semantic-release/changelog", {
      "changelogFile": "CHANGELOG.md"
    }],
    "@semantic-release/npm",
    ["@semantic-release/git", {
      "assets": ["npm-shrinkwrap.json", "package.json", "CHANGELOG.md"],
      "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
    }],
    "@semantic-release/github"
  ]
}


================================================
FILE: driver/CHANGELOG.md
================================================
# Changelog

## 3.6.0
- Update Appium XCUITest driver dependency to 10.43.1
- Update Appium UIAutomator2 driver dependency to 7.1.2

## 3.5.0
- Update Appium XCUITest driver dependency to 10.22.0
- Update Appium UIAutomator2 driver dependency to 6.8.2
- Apply formatter

## 3.4.0
- Update Appium XCUITest driver dependency to 10.18.2
- Update Appium UIAutomator2 driver dependency to 6.7.15
- Update asyncbox to 6.0.1

## 3.3.0
- Update Appium XCUITest driver dependency to 10.11.0
- Update Appium UIAutomator2 driver dependency to 6.7.1

## 3.2.0
- Fix log finding retry handling
- Update Appium XCUITest driver dependency to 10.4.3
- Update Appium UIAutomator2 driver dependency to 6.1.0

## 3.1.0
- Update Appium UIAutomator2 driver dependency to 6.0.2

## 3.0.1
- Fix types
- Update Appium XCUITest driver dependency to 10.0.1
- Update Appium UIAutomator2 driver dependency to 5.0.3

## 3.0.0
- Update Appium UIAutomator2 driver dependency to 5.0.0
- Update Appium XCUITest driver dependency to 10.0.0
- Follow Appium 3
    - Required Node.js version has been bumped to ^20.19.0 || ^22.12.0 || >=24.0.0
    - Required npm version has been bumped to >=10
    - Requires **Appium 3.0.0+**

## 2.19.0
- Update Appium UIAutomator2 driver dependency to 4.2.9
- Update Appium XCUITest driver dependency to 9.10.5

## 2.18.1/2.18.0
- Update Appium UIAutomator2 driver dependency to 4.2.4
- Update Appium XCUITest driver dependency to 9.9.1
    - Supports `appium driver run flutter download-wda-sim` command. It requires NodeJS 20+.

## 2.17.0
- Fix ending forwarding port with `forwardingPort` capability on iOS with `terminateApp`
- Update Appium XCUITest driver dependency to 9.6.1

## 2.16.0
- Added shortcut commands below. Please check the README about the usage.
    - `flutter:assertVisible`
    - `flutter:assertNotVisible`
    - `flutter:assertTappable`
- Update Appium XCUITest driver dependency to 9.2.5

## 2.15.2
- Fix device orientation change for iOS
- Update Appium XCUITest driver dependency to 9.2.4

## 2.15.1
- Add a retry logic to check `ext.flutter.driver` module existence

## 2.15.0
- Update Appium UIAutomator2 driver dependency to 4.2.3
- Update Appium XCUITest driver dependency to 9.2.3

## 2.14.2
- Fix package to include npm-shrinkwrap.json explicitly
- Update Appium UIAutomator2 driver dependency to 4.1.1
- Update Appium XCUITest driver dependency to 8.4.1

## 2.14.1
- Fix lint

## 2.14.0
- Update Appium UIAutomator2 driver dependency to 4.0.3
- Update Appium XCUITest driver dependency to 8.3.2

## 2.13.0
- Update Appium XCUITest driver dependency to 8.1.0

## 2.12.0
- Update Appium UIAutomator2 driver dependency to 3.10.0
- Update Appium XCUITest driver dependency to 7.35.1

## 2.11.0
- Adds `flutter:getTextWithCommandExtension` command. Please check the README about how to use the command properly.
- Update Appium UIAutomator2 driver dependency to 3.9.1
- Update Appium XCUITest driver dependency to 7.32.0

## 2.10.0
- Adds `flutter:dragAndDropWithCommandExtension` command. Please check the README about how to use the command properly.
- Update Appium UIAutomator2 driver dependency to 3.8.1
- Update Appium XCUITest driver dependency to 7.28.3

## 2.9.2
- Fix observatory url finding after an app activation
    - Bring `appium:maxRetryCount` and `appium:retryBackoffTime` back to use for the observaotry url findings.

## 2.9.0 (2.9.1)
- Tune syslog scanning to find the observatory url
    - Drop `appium:maxRetryCount` and `appium:retryBackoffTime` as no usage
- Update Appium UIAutomation2 driver dependency to 3.7.4
- Update XCUITest driver dependency to 7.24.1

## 2.8.0
- Support `getWidgetDiagnostics`

## 2.7.1
- add logs around set context

## 2.7.0
- Update Appium UIAutomation2 driver dependency to 3.5.4
- Update Appium XCUITest driver dependency to 7.17.5

## 2.6.0
- Update Appium XCUITest driver dependency to 7.11.1
- `doctor` command can skip Android or iOS with environment variables

## 2.5.1
- fix: scroll until visible + scroll until tapable [#671](https://github.com/appium/appium-flutter-driver/pull/671)

## 2.5.0
- Update Appium XCUITest driver dependency to 6.0.1

## 2.4.2
- Removed unused dependencies

## 2.4.1
- Removed unused dependencies

## 2.4.0
- Update Appium UIAutomation2 driver dependency to 2.42.1
- Update Appium XCUITest driver dependency to 5.14.0
- Add `doctor` command

## 2.3.0
- Update Appium UIAutomation2 driver dependency to 2.37.0
- Update Appium XCUITest driver dependency to 5.12.1

## 2.2.3
- fix leftover portforward

## 2.2.2
- fix: scrollUntilVisible and scrollUntilTapable https://github.com/appium/appium-flutter-driver/pull/622

## 2.2.1
- fix: typescript syntaxes

## 2.2.0
- Update Appium UIAutomation2 driver dependency to 2.34.1
- Update Appium XCUITest driver dependency to 5.7.0

## 2.1.0
- Update Appium XCUITest driver dependency to 5.6.0

## 2.0.0
- Update Appium XCUITest driver dependency to 5.2.0. It supports iOS 15+.

## 1.23.0
- try the latest observatory url every attempt in connectSocket

## 1.22.0
- `skipAttachObservatoryUrl` option for `mobile:activateApp` to prevent trying to attach to an observatory url after activating the app.

## 1.21.1
- `appium:maxRetryCount` is 10 by default as optimization

## 1.21.0

- Add `isolateId` capability to configure it

## 1.20.2 (1.20.0, 1.20.1 had broken lock file)
- Add `adbPort`, `remoteAdbHost` and `forwardingPort` support

## 1.19.1
- Use XCUITest driver: 4.31.0
   - Last iOS 14 and lower one work version

## 1.19.0
- Add `flutter:launchApp` to start an app via instrument service natively for iOS

## 1.18.1
- Keep using XCUITest driver v4.27.0 for iOS versions lower than 15.

## 1.18.0
- Add `flutter:connectObservatoryWsUrl` command to observe the url in the middle

## 1.17.1
- Make `mobile:activateApp` and `mobile:terminateApp` behavior same as `activateApp` and `terminateApp`

## 1.17.0
- Proxy device orientation api to NATIVE_APP context

## 1.16.0
- Update dependencies
    - UIA2 driver: 2.29.2
    - XCUITest driver: 4.32.19
- Added `clickElement` as execute script. It is the same as other `tap` commands
- Update `peerDependencies` as Appium 2.0

## 1.15.0
- Update dependencies such as UIA2/XCUITest drivers

## 1.14.4
- Fix element parsing for w3c element

## 1.14.2, 1.14.3 (the same)
- Add debug log when an exception occurred in the observatory url finding

## 1.14.1
- Bump the UIA2 version

## 1.14.0
- Fix proxyCommand for plugins [#425](https://github.com/appium-userland/appium-flutter-driver/pull/425)
- Fix startNewCommandTimeout call [#426](https://github.com/appium-userland/appium-flutter-driver/pull/426)

## 1.13.1
- Fix no `waitTimeoutMilliseconds` argument case for scrollUntilVisible/scrollUntilTapable

## 1.13.0
- Add arguments in scrollUntilVisible/scrollUntilTapable
    - `waitTimeoutMilliseconds`: The timeout to try scroll up to the timeout
    - `durationMilliseconds`: The duration to do a scroll action. `timeout` in [scroll](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/scroll.html).
    - `frequency`: The frequency to for the move events in  [scroll](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/scroll.html).


## 1.12.0
- Support `setClipboard` and `getClipboard` by always proxying the request to the NATIVE_APP context
- Support non-`app` and non-`bundleId` nor non-`appPackage` session starts as `FLUTTER` context
- Add `installApp` to install the test target after the new session request

## 1.11.0
- Add `activateApp` support to start an app

## 1.10.0
- Add `terminateApp` support to stop the running application
    - The behavior is the same as when you call the same endpoint in NATIVE_APP context

## 1.9.1
- Improved error message when no observatory url found

## 1.9.0
- **Breaking change**
    - Revert [#306](https://github.com/appium-userland/appium-flutter-driver/pull/306) (added in v1.5.0). `scrollUntilVisible` uses `waitFor` as same as before v1.5.0.
        - Please use `scrollUntilTapable` instead since this version
 - Added `scrollUntilTapable` command to scroll with `waitForTappable` [#360](https://github.com/appium-userland/appium-flutter-driver/pull/360)

## 1.8.0

Appium `2.0.0-beta.46` and higher is needed as dependencies update in dependent Appium UIA2/XCUITest drivers

- Added `timeout` argument for scrollIntoView [#358](https://github.com/appium-userland/appium-flutter-driver/pull/358)
- Added `skipPortForward` capability [#343](https://github.com/appium-userland/appium-flutter-driver/pull/343)

## 1.7.2 (1.7.1)
- Fixed `* 1000` in `scroll` [#330](https://github.com/appium-userland/appium-flutter-driver/pull/330)

## 1.7.0
- **Breaking change**
    - Do not calculate `* 1000` internally for milliseconds arguments to set them properly as same as README/examples. [#319](https://github.com/appium-userland/appium-flutter-driver/issues/319)

## 1.6.0
- Update for newer Appium 2 beta

## 1.5.0

- Use `waitForTappable` in `scrollUntilVisible` [#306](https://github.com/appium-userland/appium-flutter-driver/pull/306)

## 1.4.0

- Added `flutter:getIsolate` to get the isolate information [#298](https://github.com/appium-userland/appium-flutter-driver/pull/298)

## 1.3.0

- Added `flutter:getVMInfo` and `flutter:setIsolateId` commands to allow a session to switch the target isolate id [#292](https://github.com/appium-userland/appium-flutter-driver/pull/292)

## 1.2.0

- Added `waitForTappable` [#289](https://github.com/appium-userland/appium-flutter-driver/pull/289)

## 1.1.2 (1.1.1)

- Fixed checking observatory URL every trial [#287](https://github.com/appium-userland/appium-flutter-driver/pull/287)

## 1.1.0

- Support processLogToGetobservatory for Flutter >= 3.0.0 [#283](https://github.com/appium-userland/appium-flutter-driver/pull/283)


================================================
FILE: driver/eslint.config.mjs
================================================
import appiumConfig from '@appium/eslint-config-appium-ts';

export default [
  ...appiumConfig
];


================================================
FILE: driver/lib/commands/assertions.ts
================================================
import {FlutterDriver} from '../driver';
import {byValueKey, byText, byTooltip} from 'appium-flutter-finder';
import type {SerializableFinder} from 'appium-flutter-finder';

export type FinderInput =
  | {key: string}
  | {text: string}
  | {label: string}
  | SerializableFinder
  | string
  | {getRawFinder: () => SerializableFinder}; // FlutterElement-like input

// Serialize a finder to base64
const serializeFinder = (finder: SerializableFinder): string =>
  Buffer.from(JSON.stringify(finder)).toString('base64');

// Type guards
const isRawFinder = (input: any): input is SerializableFinder =>
  input && typeof input === 'object' && typeof input.finderType === 'string';

const isFlutterElementLike = (input: any): input is {getRawFinder: () => SerializableFinder} =>
  input && typeof input === 'object' && typeof input.getRawFinder === 'function';

// Convert FinderInput to base64 string
function getFinderBase64(input: FinderInput): string {
  if (typeof input === 'string') {
    return input; // already base64
  }

  if (isFlutterElementLike(input)) {
    return serializeFinder(input.getRawFinder());
  }

  if (isRawFinder(input)) {
    return serializeFinder(input);
  }

  if ('key' in input) {
    return byValueKey(input.key);
  }

  if ('text' in input) {
    return byText(input.text);
  }

  if ('label' in input) {
    return byTooltip(input.label);
  }

  throw new Error(
    'Invalid finder input: must provide key, text, label, raw finder, or FlutterElement',
  );
}

// Generic helper to wrap assert commands
async function executeAssertion(
  driver: FlutterDriver,
  command: string,
  input: FinderInput,
  timeout = 5000,
  extraArgs: object = {},
): Promise<void> {
  const base64 = getFinderBase64(input);
  try {
    await driver.executeElementCommand(command, base64, {
      timeout,
      ...extraArgs,
    });
  } catch (err) {
    throw new Error(`Assertion failed on command "${command}" within ${timeout}ms\n${err}`);
  }
}

// Exported assertion commands
export const assertVisible = async (
  driver: FlutterDriver,
  input: FinderInput,
  timeout = 5000,
): Promise<void> => await executeAssertion(driver, 'waitFor', input, timeout, {visible: true});

export const assertNotVisible = async (
  driver: FlutterDriver,
  input: FinderInput,
  timeout = 5000,
): Promise<void> => await executeAssertion(driver, 'waitForAbsent', input, timeout);

export const assertTappable = async (
  driver: FlutterDriver,
  input: FinderInput,
  timeout = 5000,
): Promise<void> => await executeAssertion(driver, 'waitForTappable', input, timeout);


================================================
FILE: driver/lib/commands/clipboard.ts
================================================
import type {FlutterDriver} from '../driver';

/**
 * Set clipboard content via each native app driver
 * @param this the FlutterDriver
 * @param content the content to get the clipboard
 * @param contentType the contentType to set the data type
 */
export const setClipboard = async function (
  this: FlutterDriver,
  content: string,
  contentType: string,
) {
  // @ts-expect-error this exist in xcuitestdriver or uia2 driver
  await this.proxydriver?.setClipboard(content, contentType);
};

/**
 * Get the clipboard content via each native app driver
 * @param this the FlutterDriver
 * @param contentType the contentType to set the data type
 */
export const getClipboard = async function (this: FlutterDriver, contentType: string) {
  // @ts-expect-error this exist in xcuitestdriver or uia2 driver
  await this.proxydriver?.getClipboard(contentType);
};


================================================
FILE: driver/lib/commands/context.ts
================================================
import type {FlutterDriver} from '../driver';
import {log} from '../logger';

export const FLUTTER_CONTEXT_NAME = `FLUTTER`;
export const NATIVE_CONTEXT_NAME = `NATIVE_APP`;

export const getCurrentContext = async function (this: FlutterDriver): Promise<string> {
  return this.currentContext;
};

export const setContext = async function (this: FlutterDriver, context: string) {
  if ([FLUTTER_CONTEXT_NAME, NATIVE_CONTEXT_NAME].includes(context)) {
    this.proxyWebViewActive = false;
    // Set 'native context' when flutter driver sets the context to FLUTTER_CONTEXT_NAME
    if (this.proxydriver) {
      log.debug(
        `Setting downstream drier context to '${NATIVE_CONTEXT_NAME}' in context '${context}'.`,
      );
      // @ts-expect-error this exist in xcuitestdriver or uia2 driver
      await this.proxydriver.setContext(NATIVE_CONTEXT_NAME);
    }
  } else {
    // this case may be 'webview'
    if (this.proxydriver) {
      log.debug(`Setting downstream drier context to '${context}'.`);
      // @ts-expect-error this exist in xcuitestdriver or uia2 driver
      await this.proxydriver.setContext(context);
      this.proxyWebViewActive = true;
    }
  }
  this.currentContext = context;
  if (context === FLUTTER_CONTEXT_NAME) {
    log.debug(
      `Downstream driver context is set as 'NATIVE_APP' in 'FLUTTER' context to handle the native app.`,
    );
  }
};

export const getContexts = async function (this: FlutterDriver): Promise<string[]> {
  // @ts-expect-error this exist in xcuitestdriver or uia2 driver
  const nativeContext = await this.proxydriver?.getContexts();
  if (nativeContext) {
    return [...(nativeContext as string[]), FLUTTER_CONTEXT_NAME];
  } else {
    return [FLUTTER_CONTEXT_NAME];
  }
};

export const driverShouldDoProxyCmd = function (this: FlutterDriver, command: string): boolean {
  if (!this.proxydriver) {
    return false;
  }

  if (this.currentContext === FLUTTER_CONTEXT_NAME) {
    return false;
  }

  if ([`getCurrentContext`, `setContext`, `getContexts`].includes(command)) {
    return false;
  }

  return true;
};


================================================
FILE: driver/lib/commands/element.ts
================================================
import {FlutterDriver} from '../driver';

export const getText = async function (this: FlutterDriver, el: string): Promise<string | null> {
  const response = await this.executeElementCommand(`get_text`, el);
  return response.text;
};

export const setValue = async function (
  this: FlutterDriver,
  textInput: string | [string],
  el: string,
) {
  const clickPromise = this.click(el); // acquire focus
  let text = ``;
  if (textInput instanceof Array) {
    text = textInput.reduce((previousValue, currentValue) => `${previousValue}${currentValue}`);
  } else if (typeof textInput === `string`) {
    text = textInput;
  } else {
    throw new Error(`Invalid textInput: ${textInput}`);
  }
  await clickPromise;
  await this.execute(`flutter:enterText`, [text]);
};

export const clear = async function (this: FlutterDriver, el: string) {
  await this.setValue([``], el);
};


================================================
FILE: driver/lib/commands/execute/scroll.ts
================================================
import _ from 'lodash';
import {FlutterDriver} from '../../driver';
import {waitFor, waitForTappable} from './wait';

export const scroll = async (
  self: FlutterDriver,
  elementBase64: string,
  opts: {
    dx: number;
    dy: number;
    durationMilliseconds: number;
    frequency?: number;
  },
) => {
  const {dx, dy, durationMilliseconds, frequency = 60} = opts;

  if (
    typeof dx !== `number` ||
    typeof dy !== `number` ||
    typeof durationMilliseconds !== `number` ||
    typeof frequency !== `number`
  ) {
    // @todo BaseDriver's errors.InvalidArgumentError();
    throw new Error(`${opts} is not a valid options`);
  }

  if (dx === 0 && dy === 0) {
    // @todo BaseDriver's errors.InvalidArgumentError();
    throw new Error(`${opts} is not a valid options`);
  }

  return await self.executeElementCommand(`scroll`, elementBase64, {
    dx,
    dy,
    // 'scroll' expects microseconds
    // https://github.com/flutter/flutter/blob/master/packages/flutter_driver/lib/src/common/gesture.dart#L33-L38
    duration: durationMilliseconds * 1000,
    frequency,
  });
};

export const longTap = async (
  self: FlutterDriver,
  elementBase64: string,
  options: {
    durationMilliseconds: number;
    frequency?: number;
  },
) => {
  const {durationMilliseconds = 1000, frequency = 60} = options;

  if (typeof durationMilliseconds !== 'number' || typeof frequency !== 'number') {
    throw new Error(`Invalid longTap options: ${JSON.stringify(options)}`);
  }

  return await self.executeElementCommand('scroll', elementBase64, {
    dx: 0,
    dy: 0,
    duration: durationMilliseconds * 1000,
    frequency,
  });
};

const validateOps = (alignment: any, dxScroll: any, dyScroll: any): boolean => {
  if (
    typeof alignment !== `number` ||
    typeof dxScroll !== `number` ||
    typeof dyScroll !== `number`
  ) {
    return false;
  }

  if (dxScroll === 0 && dyScroll === 0) {
    return false;
  }

  return true;
};

const shouldRetry = (startAt: number, waitTimeoutMilliseconds?: number): boolean => {
  if (!waitTimeoutMilliseconds) {
    // Then, the scroll should continue infinitely
    return true;
  }

  return Date.now() - startAt < _.toInteger(waitTimeoutMilliseconds);
};

export const scrollUntilVisible = async (
  self: FlutterDriver,
  elementBase64: string,
  opts: {
    item: string;
    alignment: number;
    dxScroll: number;
    dyScroll: number;
    durationMilliseconds: number;
    frequency?: number;
    waitTimeoutMilliseconds?: number;
  },
) => {
  const {
    item,
    alignment = 0.0,
    dxScroll = 0,
    dyScroll = 0,
    durationMilliseconds = 100,
    frequency,
    waitTimeoutMilliseconds,
  } = opts;

  if (!validateOps(alignment, dxScroll, dyScroll)) {
    throw new Error(`${opts} is not a valid options`);
  }

  // An expectation for checking that an element, known to be present on the widget tree, is visible
  let isVisible = false;
  (async () => {
    try {
      await waitFor(self, item, waitTimeoutMilliseconds);
      isVisible = true;
    } catch {}
  })();
  const startAt = Date.now();
  while (!isVisible && shouldRetry(startAt, waitTimeoutMilliseconds)) {
    try {
      await scroll(self, elementBase64, {
        dx: dxScroll,
        dy: dyScroll,
        durationMilliseconds,
        frequency,
      });
    } catch {
      /* go to the next scroll */
    }
  }

  if (!isVisible) {
    throw new Error(`Stop scrolling as timeout ${waitTimeoutMilliseconds}`);
  }

  return scrollIntoView(self, item, {alignment});
};

export const scrollUntilTapable = async (
  self: FlutterDriver,
  elementBase64: string,
  opts: {
    item: string;
    alignment: number;
    dxScroll: number;
    dyScroll: number;
    durationMilliseconds: number;
    frequency?: number;
    waitTimeoutMilliseconds?: number;
  },
) => {
  const {
    item,
    alignment = 0.0,
    dxScroll = 0,
    dyScroll = 0,
    durationMilliseconds = 100,
    frequency,
    waitTimeoutMilliseconds,
  } = opts;

  if (!validateOps(alignment, dxScroll, dyScroll)) {
    throw new Error(`${opts} is not a valid options`);
  }

  // Kick off an (unawaited) waitForTappable that will complete when the item we're
  // looking for finally scrolls onscreen and can be hit-tested. We add an initial pause to give it
  // the chance to complete if the item is already onscreen; if not, scroll
  // repeatedly until we either find the item or time out.
  let isVisible = false;
  (async () => {
    try {
      await waitForTappable(self, item, waitTimeoutMilliseconds);
      isVisible = true;
    } catch {}
  })();
  const startAt = Date.now();
  while (!isVisible && shouldRetry(startAt, waitTimeoutMilliseconds)) {
    try {
      await scroll(self, elementBase64, {
        dx: dxScroll,
        dy: dyScroll,
        durationMilliseconds,
        frequency,
      });
    } catch {
      /* go to the next scroll */
    }
  }

  if (!isVisible) {
    throw new Error(`Stop scrolling as timeout ${waitTimeoutMilliseconds}`);
  }

  return scrollIntoView(self, item, {alignment});
};

export const scrollIntoView = async (
  self: FlutterDriver,
  elementBase64: string,
  opts: {
    alignment: number;
    timeout?: number;
  },
) => {
  const {alignment = 0.0, timeout} = opts;
  if (
    typeof alignment !== `number` ||
    (typeof timeout !== `undefined` && typeof timeout !== `number`)
  ) {
    // @todo BaseDriver's errors.InvalidArgumentError();
    throw new Error(`${opts} is not a valid options`);
  }

  const args = typeof timeout === `number` ? {alignment, timeout} : {alignment};

  return await self.executeElementCommand(`scrollIntoView`, elementBase64, args);
};


================================================
FILE: driver/lib/commands/execute/wait.ts
================================================
import {FlutterDriver} from '../../driver';

const waitForConstructor =
  (command: `waitForAbsent` | `waitFor` | `waitForTappable`) =>
  async (
    self: FlutterDriver,
    elementBase64: string,
    durationMilliseconds?: number,
  ): Promise<string> => {
    let args = {};

    if (typeof durationMilliseconds === `number`) {
      args = {
        timeout: durationMilliseconds,
      };
    } else if (typeof durationMilliseconds !== `undefined`) {
      // @todo BaseDriver's errors.InvalidArgumentError();
      throw new Error(`durationMilliseconds is not a valid options`);
    }

    await self.executeElementCommand(command, elementBase64, args);
    return elementBase64;
  };

export const waitForAbsent = waitForConstructor(`waitForAbsent`);

export const waitFor = waitForConstructor(`waitFor`);

export const waitForTappable = waitForConstructor(`waitForTappable`);


================================================
FILE: driver/lib/commands/execute.ts
================================================
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type {FlutterDriver} from '../driver';
import {reConnectFlutterDriver} from '../sessions/session';
import {
  longTap,
  scroll,
  scrollIntoView,
  scrollUntilVisible,
  scrollUntilTapable,
} from './execute/scroll';
import {waitFor, waitForAbsent, waitForTappable} from './execute/wait';
import {assertVisible, assertNotVisible, assertTappable, type FinderInput} from './assertions';

import {launchApp} from './../ios/app';
import B from 'bluebird';

const flutterCommandRegex = /^[\s]*flutter[\s]*:(.+)/;

// Define types for better type safety
type CommandHandler = (driver: FlutterDriver, ...args: any[]) => Promise<any>;
type CommandMap = Record<string, CommandHandler>;

interface DragAndDropParams {
  startX: string;
  startY: string;
  endX: string;
  endY: string;
  duration: string;
}

interface DiagnosticsOptions {
  subtreeDepth?: number;
  includeProperties?: boolean;
}

interface LongTapOptions {
  durationMilliseconds: number;
  frequency?: number;
}

interface OffsetOptions {
  offsetType: 'bottomLeft' | 'bottomRight' | 'center' | 'topLeft' | 'topRight';
}

// Extract command handlers into a separate object for better organization
const commandHandlers: CommandMap = {
  launchApp: async (driver, appId: string, opts = {}) => {
    const {arguments: args = [], environment: env = {}} = opts;
    await launchApp(driver.internalCaps.udid!, appId, args, env);
    await reConnectFlutterDriver.bind(driver)(driver.internalCaps);
  },
  connectObservatoryWsUrl: async (driver) => {
    await reConnectFlutterDriver.bind(driver)(driver.internalCaps);
  },
  checkHealth: async (driver) => (await driver.executeElementCommand('get_health')).status,
  getVMInfo: async (driver) => await driver.executeGetVMCommand(),
  getRenderTree: async (driver) => (await driver.executeElementCommand('get_render_tree')).tree,
  getOffset: async (driver, elementBase64: string, options: OffsetOptions) =>
    await driver.executeElementCommand('get_offset', elementBase64, options),
  waitForCondition: async (driver, conditionName: string) =>
    await driver.executeElementCommand('waitForCondition', '', {
      conditionName,
    }),
  forceGC: async (driver) => {
    const response = (await driver.socket!.call('_collectAllGarbage', {
      isolateId: driver.socket!.isolateId,
    })) as {type: string};
    if (response.type !== 'Success') {
      throw new Error(`Could not forceGC, response was ${JSON.stringify(response)}`);
    }
  },
  setIsolateId: async (driver, isolateId: string) => {
    driver.socket!.isolateId = isolateId;
    return await driver.socket!.call('getIsolate', {isolateId});
  },
  getIsolate: async (driver, isolateId?: string) =>
    await driver.executeGetIsolateCommand(isolateId || driver.socket!.isolateId!),
  clearTimeline: async (driver) => {
    const call1 = driver.socket!.call('_clearVMTimeline');
    const call2 = driver.socket!.call('clearVMTimeline');
    const response = await B.any([call1, call2]);
    if (response.type !== 'Success') {
      throw new Error(`Could not clear timeline, response was ${JSON.stringify(response)}`);
    }
  },
  getRenderObjectDiagnostics: async (
    driver,
    elementBase64: string,
    opts: DiagnosticsOptions = {},
  ) => {
    const {subtreeDepth = 0, includeProperties = true} = opts;
    return await driver.executeElementCommand('get_diagnostics_tree', elementBase64, {
      diagnosticsType: 'renderObject',
      includeProperties,
      subtreeDepth,
    });
  },
  getWidgetDiagnostics: async (driver, elementBase64: string, opts: DiagnosticsOptions = {}) => {
    const {subtreeDepth = 0, includeProperties = true} = opts;
    return await driver.executeElementCommand('get_diagnostics_tree', elementBase64, {
      diagnosticsType: 'widget',
      includeProperties,
      subtreeDepth,
    });
  },
  getSemanticsId: async (driver, elementBase64: string) =>
    (await driver.executeElementCommand('get_semantics_id', elementBase64)).id,
  waitForAbsent: async (driver, finder: string, timeout?: number) =>
    await waitForAbsent(driver, finder, timeout),
  waitFor: async (driver, finder: string, timeout?: number) =>
    await waitFor(driver, finder, timeout),
  waitForTappable: async (driver, finder: string, timeout?: number) =>
    await waitForTappable(driver, finder, timeout),
  scroll: async (driver, finder: string, opts: any) => await scroll(driver, finder, opts),
  scrollUntilVisible: async (driver, finder: string, opts: any) =>
    await scrollUntilVisible(driver, finder, opts),
  scrollUntilTapable: async (driver, finder: string, opts: any) =>
    await scrollUntilTapable(driver, finder, opts),
  scrollIntoView: async (driver, finder: string, opts: any) =>
    await scrollIntoView(driver, finder, opts),
  setTextEntryEmulation: async (driver, enabled: boolean) =>
    await driver.socket!.executeSocketCommand({
      command: 'set_text_entry_emulation',
      enabled,
    }),
  enterText: async (driver, text: string) =>
    await driver.socket!.executeSocketCommand({command: 'enter_text', text}),
  requestData: async (driver, message: string) =>
    await driver.socket!.executeSocketCommand({
      command: 'request_data',
      message,
    }),
  longTap: async (driver, finder: string, durationOrOptions: LongTapOptions) =>
    await longTap(driver, finder, durationOrOptions),
  waitForFirstFrame: async (driver) =>
    await driver.executeElementCommand('waitForCondition', '', {
      conditionName: 'FirstFrameRasterizedCondition',
    }),
  setFrameSync: async (driver, enabled: boolean, durationMilliseconds: number) =>
    await driver.socket!.executeSocketCommand({
      command: 'set_frame_sync',
      enabled,
      timeout: durationMilliseconds,
    }),
  clickElement: async (driver, finder: string, opts: {timeout?: number} = {}) => {
    const {timeout = 1000} = opts;
    return await driver.executeElementCommand('tap', finder, {timeout});
  },
  dragAndDropWithCommandExtension: async (driver, params: DragAndDropParams) =>
    await driver.socket!.executeSocketCommand({
      command: 'dragAndDropWithCommandExtension',
      ...params,
    }),
  assertVisible: async (driver, input: FinderInput, timeout = 5000) =>
    await assertVisible(driver, input, timeout),
  assertNotVisible: async (driver, input: FinderInput, timeout = 5000) =>
    await assertNotVisible(driver, input, timeout),
  assertTappable: async (driver, input: FinderInput, timeout = 5000) =>
    await assertTappable(driver, input, timeout),
  getTextWithCommandExtension: async (driver, params: {findBy: string}) =>
    await driver.socket!.executeSocketCommand({
      command: 'getTextWithCommandExtension',
      findBy: params.findBy,
    }),
};
export const execute = async function (this: FlutterDriver, rawCommand: string, args: any[]) {
  const matching = rawCommand.match(flutterCommandRegex);
  if (!matching) {
    throw new Error(`Command not supported: "${rawCommand}"`);
  }

  const command = matching[1].trim();
  const handler = commandHandlers[command];

  if (!handler) {
    throw new Error(`Command not supported: "${rawCommand}"`);
  }

  return await handler(this, ...args);
};


================================================
FILE: driver/lib/commands/gesture.ts
================================================
import type {FlutterDriver} from '../driver';
import {longTap as longClick} from './execute/scroll';

export const click = async function (this: FlutterDriver, el: string) {
  const retVal = await this.tapEl(el, false);
  return retVal;
};

export const tapEl = async function (this: FlutterDriver, el: string, longPress: boolean) {
  // perform a tap on the given element
  // if longPress is true, the tap becomes a longPress action
  const commandName = longPress ? `longPress` : `tap`;
  return await this.executeElementCommand(commandName, el);
};

export const tap = async function (
  this: FlutterDriver,
  gestures: Record<string, any>[],
  longPress: boolean,
) {
  // parse the given gestures array to call the appropriate tap method
  // if longPress is true, the tap is a long press action
  const elementId = gestures[0].options.element;
  await this.tapEl(elementId, longPress);
};

export const longTap = async function (
  this: FlutterDriver,
  gestures: Record<string, any>[],
  ms: number,
) {
  // pass duration if the wait action given by user.
  // If wait action is missing taking 10000 ms default
  const elementId = gestures[0].options.element;
  return await longClick(this, elementId, {
    durationMilliseconds: ms,
    frequency: 30,
  });
};

export const performTouch = async function (this: FlutterDriver, gestures: Record<string, any>[]) {
  if (gestures.length === 3) {
    if (
      gestures[0].action === `longPress` &&
      gestures[1].action === `wait` &&
      gestures[2].action === `release`
    ) {
      return await this.longTap(gestures, gestures[1].options.ms);
    }
  } else if (gestures.length === 2) {
    if (gestures[0].action === `press` && gestures[1].action === `release`) {
      return await this.tap(gestures, false);
    } else if (gestures[0].action === `longPress` && gestures[1].action === `release`) {
      return await this.longTap(gestures, 10 * 1000);
    }
  } else if (gestures.length === 1) {
    if (gestures[0].action === `tap`) {
      return await this.tap(gestures, false);
    }

    if (gestures[0].action === `longPress`) {
      return await this.longTap(gestures, 10 * 1000);
    }
  }
};


================================================
FILE: driver/lib/commands/screen.ts
================================================
import type {FlutterDriver} from '../driver';
import {IsolateSocket} from '../sessions/isolate_socket';

export const getScreenshot = async function (this: FlutterDriver) {
  const response = (await (this.socket as IsolateSocket).call(`_flutter.screenshot`)) as any;
  return response.screenshot;
};


================================================
FILE: driver/lib/desired-caps.ts
================================================
export const desiredCapConstraints = {
  app: {
    isString: true,
  },
  avd: {
    isString: true,
  },
  maxRetryCount: {
    isNumber: true,
  },
  platformName: {
    inclusionCaseInsensitive: ['iOS', 'Android'],
    isString: true,
    presence: true,
  },
  retryBackoffTime: {
    isNumber: true,
  },
  udid: {
    isString: true,
  },
  observatoryWsUri: {
    isString: true,
  },
  skipPortForward: {
    isBoolean: true,
  },
  adbPort: {
    isNumber: true,
  },
  remoteAdbHost: {
    isString: true,
  },
  forwardingPort: {
    isNumber: true,
  },
} as const;


================================================
FILE: driver/lib/doctor/checks.js
================================================
import {doctor as androidDoctor} from 'appium-android-driver';
import {doctor as iosDoctor} from 'appium-xcuitest-driver';

// shared
export const homeEnvVarCheck = /** @type {any} */ (iosDoctor.required.homeEnvVarCheck);

let androidHomeCheck;
let javaHomeCheck;
let javaHomeValueCheck;
let androidSdkCheck;
let optionalBundletoolCheck;
let optionalGstreamerCheck;
if (!process.env.SKIP_ANDROID) {
  androidHomeCheck = androidDoctor.androidHomeCheck;
  javaHomeCheck = androidDoctor.javaHomeCheck;
  javaHomeValueCheck = androidDoctor.javaHomeValueCheck;
  androidSdkCheck = androidDoctor.androidSdkCheck;
  optionalBundletoolCheck = androidDoctor.optionalBundletoolCheck;
  optionalGstreamerCheck = androidDoctor.optionalGstreamerCheck;
}

let xcodeCheck;
let xcodeToolsCheck;
let envVarAndPathCheck;
let optionalApplesimutilsCommandCheck;
if (!process.env.SKIP_IOS) {
  xcodeCheck = iosDoctor.required.xcodeCheck;
  xcodeToolsCheck = iosDoctor.required.xcodeToolsCheck;
  envVarAndPathCheck = /** @type {any} */ (iosDoctor.required.homeEnvVarCheck);
  optionalApplesimutilsCommandCheck = iosDoctor.optional.optionalApplesimutilsCheck;
}

// shared
export const optionalFfmpegCheck = androidDoctor.optionalFfmpegCheck;

export {
  androidHomeCheck,
  javaHomeCheck,
  javaHomeValueCheck,
  androidSdkCheck,
  optionalBundletoolCheck,
  optionalGstreamerCheck,
  xcodeCheck,
  xcodeToolsCheck,
  envVarAndPathCheck,
  optionalApplesimutilsCommandCheck,
};


================================================
FILE: driver/lib/driver.ts
================================================
// @ts-ignore: no 'errors' export module
import _ from 'lodash';
import {BaseDriver} from 'appium/driver';
import {log as logger} from './logger';
import {
  executeElementCommand,
  executeGetVMCommand,
  executeGetIsolateCommand,
} from './sessions/observatory';
import {PLATFORM} from './platform';
import {createSession, reConnectFlutterDriver} from './sessions/session';
import {
  driverShouldDoProxyCmd,
  FLUTTER_CONTEXT_NAME,
  getContexts,
  getCurrentContext,
  NATIVE_CONTEXT_NAME,
  setContext,
} from './commands/context';
import {clear, getText, setValue} from './commands/element';
import {execute} from './commands/execute';
import {click, longTap, performTouch, tap, tapEl} from './commands/gesture';
import {getScreenshot} from './commands/screen';
import {getClipboard, setClipboard} from './commands/clipboard';
import {desiredCapConstraints} from './desired-caps';
import {XCUITestDriver} from 'appium-xcuitest-driver';
import {AndroidUiautomator2Driver} from 'appium-uiautomator2-driver';
import type {
  DefaultCreateSessionResult,
  DriverCaps,
  DriverData,
  W3CDriverCaps,
  RouteMatcher,
  Orientation,
} from '@appium/types';
import type {IsolateSocket} from './sessions/isolate_socket';
import type {Server} from 'node:net';
import type {LogMonitor} from './sessions/log-monitor';

type FluttertDriverConstraints = typeof desiredCapConstraints;
// Need to not proxy in WebView context
const WEBVIEW_NO_PROXY = [
  [`GET`, new RegExp(`^/session/[^/]+/appium`)],
  [`GET`, new RegExp(`^/session/[^/]+/context`)],
  [`GET`, new RegExp(`^/session/[^/]+/element/[^/]+/rect`)],
  [`GET`, new RegExp(`^/session/[^/]+/log/types$`)],
  [`GET`, new RegExp(`^/session/[^/]+/orientation`)],
  [`POST`, new RegExp(`^/session/[^/]+/appium`)],
  [`POST`, new RegExp(`^/session/[^/]+/context`)],
  [`POST`, new RegExp(`^/session/[^/]+/log$`)],
  [`POST`, new RegExp(`^/session/[^/]+/orientation`)],
  [`POST`, new RegExp(`^/session/[^/]+/touch/multi/perform`)],
  [`POST`, new RegExp(`^/session/[^/]+/touch/perform`)],
] as import('@appium/types').RouteMatcher[];

class FlutterDriver extends BaseDriver<FluttertDriverConstraints> {
  public socket: IsolateSocket | null;
  public locatorStrategies = [`key`, `css selector`];
  public proxydriver: XCUITestDriver | AndroidUiautomator2Driver | null;
  public device: any;

  public portForwardLocalPort: string | null;
  public localServer: Server | null;
  protected _logmon: LogMonitor | null;

  // Used to keep the capabilities internally
  public internalCaps: DriverCaps<FluttertDriverConstraints>;

  public receiveAsyncResponse: (...args: any[]) => Promise<any>;

  // to handle WebView context
  public proxyWebViewActive = false;

  // session
  public executeElementCommand = executeElementCommand;
  public executeGetVMCommand = executeGetVMCommand;
  public executeGetIsolateCommand = executeGetIsolateCommand;
  public execute = execute;
  public executeAsync = execute;

  // element
  public getText = getText;
  public setValue = setValue;
  public clear = clear;
  public getScreenshot = getScreenshot;

  // gesture
  public click = click;
  public longTap = longTap;
  public tapEl = tapEl;
  public tap = tap;
  public performTouch = performTouch;

  // context

  public getContexts = getContexts;
  public getCurrentContext = getCurrentContext;
  public setContext = setContext;
  protected currentContext = FLUTTER_CONTEXT_NAME;
  private driverShouldDoProxyCmd = driverShouldDoProxyCmd;

  // content
  public getClipboard = getClipboard;
  public setClipboard = setClipboard;

  constructor(opts, shouldValidateCaps: boolean) {
    super(opts, shouldValidateCaps);
    this.socket = null;
    this.device = null;
    this._logmon = null;
    this.desiredCapConstraints = desiredCapConstraints;

    // Used to keep the port for port forward to clear the pair.
    this.portForwardLocalPort = null;

    // Used for iOS to end the local server to proxy the request.
    this.localServer = null;
  }

  public async createSession(
    ...args
  ): Promise<DefaultCreateSessionResult<FluttertDriverConstraints>> {
    const [sessionId, caps] = await super.createSession(
      ...(JSON.parse(JSON.stringify(args)) as [
        W3CDriverCaps,
        W3CDriverCaps,
        W3CDriverCaps,
        DriverData[],
      ]),
    );
    this.internalCaps = caps;
    return createSession.bind(this)(sessionId, caps, ...JSON.parse(JSON.stringify(args)));
  }

  public async deleteSession() {
    this.log.info(`Deleting Flutter Driver session`);

    this._logmon?.stop();
    this._logmon = null;
    this.proxydriver?.eventEmitter?.removeAllListeners('syslogStarted');

    this.log.info('Cleanup the port forward');
    switch (_.toLower(this.internalCaps.platformName)) {
      case PLATFORM.IOS:
        this.localServer?.close();
        this.localServer = null;
        break;
      case PLATFORM.ANDROID:
        if (this.portForwardLocalPort) {
          if (this.proxydriver) {
            await (this.proxydriver as AndroidUiautomator2Driver).adb?.removePortForward(
              this.portForwardLocalPort,
            );
          }
        }
        break;
    }

    if (this.proxydriver) {
      this.log.info('Deleting the proxy driver session.');
      try {
        await this.proxydriver.deleteSession(this.sessionId || undefined);
      } catch (e) {
        this.log.warn(e.message);
      }
      this.proxydriver = null;
    }

    await super.deleteSession();
  }

  public async installApp(appPath: string, opts = {}) {
    // @ts-expect-error this exist in xcuitestdriver or uia2 driver
    this.proxydriver?.installApp(appPath, opts);
  }

  public async activateApp(appId: string) {
    // @ts-expect-error this exist in xcuitestdriver or uia2 driver
    this.proxydriver?.activateApp(appId);
    await reConnectFlutterDriver.bind(this)(this.internalCaps);
  }

  public async terminateApp(appId: string) {
    // @ts-expect-error this exist in xcuitestdriver or uia2 driver
    return await this.proxydriver?.terminateApp(appId);
  }

  public async back() {
    // @ts-expect-error this exist in xcuitestdriver or uia2 driver
    return await this.proxydriver?.back();
  }

  public async getOrientation(): Promise<string | null> {
    if (!this.proxydriver) {
      return null;
    }
    switch (_.toLower(this.internalCaps.platformName)) {
      case PLATFORM.IOS:
        return await (this.proxydriver as XCUITestDriver).proxyCommand('/orientation', 'GET');
      default:
        return await (this.proxydriver as AndroidUiautomator2Driver).getOrientation();
    }
  }

  public async setOrientation(orientation: string) {
    switch (_.toLower(this.internalCaps.platformName)) {
      case PLATFORM.IOS:
        return await (this.proxydriver as XCUITestDriver).proxyCommand('/orientation', 'POST', {
          orientation,
        });
      default:
        return await (this.proxydriver as AndroidUiautomator2Driver).setOrientation(
          orientation as Orientation,
        );
    }
  }

  public validateLocatorStrategy(strategy: string) {
    // @todo refactor DRY
    if (this.currentContext === `NATIVE_APP`) {
      return this.proxydriver?.validateLocatorStrategy(strategy);
    }
    super.validateLocatorStrategy(strategy, false);
  }

  validateDesiredCaps(
    caps: DriverCaps<FluttertDriverConstraints>,
  ): caps is DriverCaps<FluttertDriverConstraints> {
    // check with the base class, and return if it fails
    const res = super.validateDesiredCaps(caps);
    if (!res) {
      return res;
    }

    // finally, return true since the superclass check passed, as did this
    return true;
  }

  public async proxyCommand(url: string, method: string, body = null) {
    // @ts-expect-error this exist in xcuitestdriver or uia2 driver
    const result = await this.proxydriver?.proxyCommand(url, method, body);
    return result;
  }

  public async executeCommand(
    cmd: string,
    ...args: [string, [{skipAttachObservatoryUrl: string; any: any}]]
  ) {
    if (new RegExp(/^[\s]*mobile:[\s]*activateApp$/).test(args[0])) {
      const {skipAttachObservatoryUrl = false} = args[1][0];
      await this.proxydriver?.executeCommand(cmd, ...args);
      if (skipAttachObservatoryUrl) {
        return;
      }
      await reConnectFlutterDriver.bind(this)(this.internalCaps);
      return;
    } else if (new RegExp(/^[\s]*mobile:[\s]*terminateApp$/).test(args[0])) {
      // to make the behavior as same as this.terminateApp
      return await this.proxydriver?.executeCommand(cmd, ...args);
    } else if (cmd === `receiveAsyncResponse`) {
      logger.debug(`Executing FlutterDriver response '${cmd}'`);
      return await this.receiveAsyncResponse(...args);
    } else if ([`setOrientation`, `getOrientation`, `back`].includes(cmd)) {
      // The `setOrientation` and `getOrientation` commands are handled differently
      // for iOS and Android platforms. These commands are deferred to the base driver's
      // implementation (`super.executeCommand`) to ensure compatibility with both platforms
      // and to leverage the platform-specific logic already implemented in the base driver.
      logger.debug(`Executing FlutterDriver command '${cmd}'`);
      return await super.executeCommand(cmd, ...args);
    } else {
      if (this.driverShouldDoProxyCmd(cmd)) {
        logger.debug(`Executing proxied driver command '${cmd}'`);

        // There are 2 CommandTimeout (FlutterDriver and proxy)
        // Only FlutterDriver CommandTimeout is used; Proxy is disabled
        // All proxy commands needs to reset the FlutterDriver CommandTimeout
        // Here we manually reset the FlutterDriver CommandTimeout for commands that goes to proxy.
        this.clearNewCommandTimeout();
        const result = await this.proxydriver?.executeCommand(cmd, ...args);
        this.startNewCommandTimeout();
        return result;
      } else {
        logger.debug(`Executing Flutter driver command '${cmd}'`);
        return await super.executeCommand(cmd, ...args);
      }
    }
  }

  public getProxyAvoidList(): RouteMatcher[] {
    if ([FLUTTER_CONTEXT_NAME, NATIVE_CONTEXT_NAME].includes(this.currentContext)) {
      return [];
    }

    return WEBVIEW_NO_PROXY;
  }

  public proxyActive(): boolean {
    // In WebView context, all request should got to each driver
    // so that they can handle http request properly.
    // On iOS, WebView context is handled by XCUITest driver while Android is by chromedriver.
    // It means XCUITest driver should keep the XCUITest driver as a proxy,
    // while UIAutomator2 driver should proxy to chromedriver instead of UIA2 proxy.
    return this.proxyWebViewActive && this.proxydriver?.constructor.name !== XCUITestDriver.name;
  }

  public canProxy(): boolean {
    // As same as proxyActive, all request should got to each driver
    // so that they can handle http request properly
    return this.proxyWebViewActive;
  }
}

export {FlutterDriver};


================================================
FILE: driver/lib/ios/app.ts
================================================
import {services, INSTRUMENT_CHANNEL} from 'appium-ios-device';
import {log} from './../logger';

/**
 * Launch the given bundle id via instrument service.
 */
export const launchApp = async (
  udid: string,
  bundleId: string,
  args = [],
  env = {},
): Promise<boolean> => {
  let instrumentService;
  try {
    instrumentService = await services.startInstrumentService(udid);
    log.info(
      `Launching app ${bundleId} with arguments ${JSON.stringify(args)} and env ${JSON.stringify(env)} on device ${udid}`,
    );
    await instrumentService.callChannel(
      INSTRUMENT_CHANNEL.PROCESS_CONTROL,
      'launchSuspendedProcessWithDevicePath:bundleIdentifier:environment:arguments:options:',
      '',
      bundleId,
      env,
      args,
      {StartSuspendedKey: 0, KillExisting: 1},
    );
    return true;
  } catch (err) {
    log.warn(`Failed to launch '${bundleId}'. Original error: ${err.stderr || err.message}`);
    return false;
  } finally {
    if (instrumentService) {
      instrumentService.close();
    }
  }
};


================================================
FILE: driver/lib/logger.ts
================================================
import {logger} from '@appium/support';
import {AppiumLogger} from '@appium/types';

export const log: AppiumLogger = logger.getLogger(`FlutterDriver`);


================================================
FILE: driver/lib/platform.ts
================================================
export const PLATFORM = {
  IOS: 'ios',
  ANDROID: 'android',
} as const;


================================================
FILE: driver/lib/sessions/android.ts
================================================
import {AndroidUiautomator2Driver} from 'appium-uiautomator2-driver';
import {connectSocket, extractObservatoryUrl, OBSERVATORY_URL_PATTERN} from './observatory';
import type {InitialOpts, StringRecord} from '@appium/types';
import type {IsolateSocket} from './isolate_socket';
import {FlutterDriver} from '../driver';
import {LogMonitor} from './log-monitor';
import type {LogEntry} from './log-monitor';

export async function startAndroidSession(
  this: FlutterDriver,
  caps: Record<string, any>,
  ...args: any[]
): Promise<[AndroidUiautomator2Driver, IsolateSocket | null]> {
  this.log.info(`Starting an Android proxy session`);
  const androiddriver = new AndroidUiautomator2Driver({} as InitialOpts);
  if (!caps.observatoryWsUri) {
    androiddriver.eventEmitter.once('syslogStarted', (syslog) => {
      this._logmon = new LogMonitor(syslog, async (entry: LogEntry) => {
        if (extractObservatoryUrl(entry)) {
          this.log.debug(`Matched the syslog line '${entry.message}'`);
          return true;
        }
        return false;
      });
      this._logmon.start();
    });
  }
  //@ts-ignore Args are ok
  await androiddriver.createSession(...args);

  // the session starts without any apps
  if (caps.app === undefined && caps.appPackage === undefined) {
    return [androiddriver, null];
  }

  return [androiddriver, await connectAndroidSession.bind(this)(androiddriver, caps)];
}

export async function connectAndroidSession(
  this: FlutterDriver,
  androiddriver: AndroidUiautomator2Driver,
  caps: Record<string, any>,
  clearLog: boolean = false,
): Promise<IsolateSocket> {
  const observatoryWsUri = await getObservatoryWsUri.bind(this)(androiddriver, caps, clearLog);
  return await connectSocket.bind(this)(observatoryWsUri, caps);
}

export async function getObservatoryWsUri(
  this: FlutterDriver,
  proxydriver: AndroidUiautomator2Driver,
  caps: StringRecord,
  clearLog: boolean = false,
): Promise<string> {
  if (clearLog) {
    this._logmon?.clearlastMatch();
    this._logmon?.stop();
    this._logmon?.start();
  }

  let urlObject: URL;
  if (caps.observatoryWsUri) {
    urlObject = new URL(caps.observatoryWsUri);
    urlObject.protocol = `ws`;

    // defaults to skip the port-forwarding as backward compatibility
    if (caps.skipPortForward === undefined || caps.skipPortForward) {
      return urlObject.toJSON();
    }
  } else {
    if (!this._logmon) {
      throw new Error(
        `The mandatory logcat service must be running in order to initialize the Flutter driver. ` +
          `Have you disabled it in capabilities?`,
      );
    }

    let lastMatch: LogEntry | null = null;
    try {
      lastMatch = await this._logmon.waitForLastMatchExist(
        caps.maxRetryCount,
        caps.retryBackoffTime,
      );
    } catch (e) {
      this.log.error(e);
    }
    if (!lastMatch) {
      throw new Error(
        `No observatory URL matching to '${OBSERVATORY_URL_PATTERN}' was found in the device log. ` +
          `Please make sure the application under test is configured properly according to ` +
          `https://github.com/appium/appium-flutter-driver#usage and that it does not crash on startup.`,
      );
    }
    urlObject = extractObservatoryUrl(lastMatch) as URL;
  }
  const remotePort = urlObject.port;
  this.portForwardLocalPort = caps.forwardingPort ?? remotePort;
  urlObject.port = this.portForwardLocalPort as string;
  await proxydriver.adb.forwardPort(this.portForwardLocalPort as string, remotePort);
  if (!caps.observatoryWsUri && proxydriver.adb.adbHost) {
    urlObject.host = proxydriver.adb.adbHost;
  }
  return urlObject.toJSON();
}


================================================
FILE: driver/lib/sessions/base64url.ts
================================================
import _ from 'lodash';
import {util} from '@appium/support';

export const decode = (
  input: string | {ELEMENT: string} | {[util.W3C_WEB_ELEMENT_IDENTIFIER]: string},
): string => {
  let base64String: string = ``;
  if (_.isString(input)) {
    base64String = input as string;
  } else if (_.has(input, util.W3C_WEB_ELEMENT_IDENTIFIER)) {
    base64String = input[util.W3C_WEB_ELEMENT_IDENTIFIER] as string;
  } else if (_.has(input, 'ELEMENT')) {
    // @ts-ignore TS2339
    base64String = input.ELEMENT as string;
  } else {
    throw new Error(
      `Input is expected to be a base64-encoded string or a valid element object. ` +
        `${JSON.stringify(input)} has been provided instead`,
    );
  }
  return Buffer.from(base64String, `base64`).toString();
};


================================================
FILE: driver/lib/sessions/ios.ts
================================================
import {utilities} from 'appium-ios-device';
import {XCUITestDriver} from 'appium-xcuitest-driver';
import B from 'bluebird';
import net from 'node:net';
import {checkPortStatus} from 'portscanner';
import {connectSocket, extractObservatoryUrl, OBSERVATORY_URL_PATTERN} from './observatory';
import type {IsolateSocket} from './isolate_socket';
import {LogMonitor} from './log-monitor';
import type {LogEntry} from './log-monitor';
import type {FlutterDriver} from '../driver';
import type {XCUITestDriverOpts} from 'appium-xcuitest-driver/build/lib/driver';

const LOCALHOST = `127.0.0.1`;

export async function startIOSSession(
  this: FlutterDriver,
  caps: Record<string, any>,
  ...args: any[]
): Promise<[XCUITestDriver, IsolateSocket | null]> {
  this.log.info(`Starting an IOS proxy session`);
  const iosdriver = new XCUITestDriver({} as XCUITestDriverOpts);
  if (!caps.observatoryWsUri) {
    iosdriver.eventEmitter.once('syslogStarted', (syslog) => {
      this._logmon = new LogMonitor(syslog, async (entry: LogEntry) => {
        if (extractObservatoryUrl(entry)) {
          this.log.debug(`Matched the syslog line '${entry.message}'`);
          return true;
        }
        return false;
      });
      this._logmon.start();
    });
  }
  // @ts-expect-error can be ignored
  await iosdriver.createSession(...args);

  // the session starts without any apps
  if (caps.app === undefined && caps.bundleId === undefined) {
    return [iosdriver, null];
  }

  return [iosdriver, await connectIOSSession.bind(this)(iosdriver, caps)];
}

export async function connectIOSSession(
  this: FlutterDriver,
  iosdriver: XCUITestDriver,
  caps: Record<string, any>,
  clearLog: boolean = false,
): Promise<IsolateSocket> {
  const observatoryWsUri = await getObservatoryWsUri.bind(this)(iosdriver, caps, clearLog);
  return await connectSocket.bind(this)(observatoryWsUri, iosdriver, caps);
}

async function requireFreePort(this: FlutterDriver, port: number) {
  // Try to close existing local server if it exists
  if (this.localServer) {
    this.log.info(`Closing existing local server on port ${port}`);
    await new Promise<void>((resolve) => {
      this.localServer?.close((err) => {
        if (err) {
          this.log.error(`Error occurred while closing the local server: ${err.message}`);
          return resolve(); // Resolve even if there's an error to avoid hanging
        }
        this.log.info(`Previous local server closed`);
        resolve();
      });
    });
  }
  if ((await checkPortStatus(port, LOCALHOST)) !== `open`) {
    return;
  }
  this.log.warn(`Port #${port} is busy. Did you quit the previous driver session(s) properly?`);
  throw new Error(
    `The port :${port} is occupied by an other process. ` +
      `You can either quit that process or select another free port.`,
  );
}

export async function getObservatoryWsUri(
  this: FlutterDriver,
  proxydriver: XCUITestDriver,
  caps: Record<string, any>,
  clearLog: boolean = false,
): Promise<string> {
  if (clearLog) {
    this._logmon?.clearlastMatch();
    this._logmon?.stop();
    this._logmon?.start();
  }

  let urlObject;
  if (caps.observatoryWsUri) {
    urlObject = new URL(caps.observatoryWsUri);
    urlObject.protocol = `ws`;

    // defaults to skip the port-forwarding as backward compatibility
    if (caps.skipPortForward === undefined || caps.skipPortForward) {
      return urlObject.toJSON();
    }
  } else {
    if (!this._logmon) {
      throw new Error(
        `The mandatory syslog service must be running in order to initialize the Flutter driver. ` +
          `Have you disabled it in capabilities?`,
      );
    }

    let lastMatch: LogEntry | null = null;
    try {
      lastMatch = await this._logmon.waitForLastMatchExist(
        caps.maxRetryCount,
        caps.retryBackoffTime,
      );
    } catch (e) {
      this.log.error(e);
    }
    if (!lastMatch) {
      throw new Error(
        `No observatory URL matching to '${OBSERVATORY_URL_PATTERN}' was found in the device log. ` +
          `Please make sure the application under test is configured properly according to ` +
          `https://github.com/appium/appium-flutter-driver#usage and that it does not crash on startup.`,
      );
    }
    urlObject = extractObservatoryUrl(lastMatch) as URL;
  }
  if (!proxydriver.isRealDevice()) {
    this.log.info(`Running on iOS simulator`);
    return urlObject.toJSON();
  }

  const remotePort = urlObject.port;
  const localPort = caps.forwardingPort ?? remotePort;
  urlObject.port = localPort;

  this.log.info(`Running on iOS real device`);
  const {udid} = proxydriver.opts;
  await requireFreePort.bind(this)(localPort);
  this.localServer = net.createServer(async (localSocket) => {
    let remoteSocket;
    try {
      remoteSocket = await utilities.connectPort(udid, remotePort);
    } catch {
      localSocket.destroy();
      return;
    }

    const destroyCommChannel = () => {
      remoteSocket.unpipe(localSocket);
      localSocket.unpipe(remoteSocket);
    };
    remoteSocket.once(`close`, () => {
      destroyCommChannel();
      localSocket.destroy();
    });
    remoteSocket.on('error', (e) => this.log.debug(e));

    localSocket.once(`end`, destroyCommChannel);
    localSocket.once(`close`, () => {
      destroyCommChannel();
      remoteSocket.destroy();
    });
    localSocket.on('error', (e) => this.log.warn(e.message));
    localSocket.pipe(remoteSocket);
    remoteSocket.pipe(localSocket);
  });
  const listeningPromise = new B((resolve, reject) => {
    this.localServer?.once(`listening`, resolve);
    this.localServer?.once(`error`, reject);
  });
  this.localServer?.listen(localPort);
  try {
    await listeningPromise;
  } catch (e) {
    this.localServer = null;
    throw new Error(`Cannot listen on the local port ${localPort}. Original error: ${e.message}`);
  }

  this.log.info(`Forwarding the remote port ${remotePort} to the local port ${localPort}`);

  process.on(`beforeExit`, () => {
    this.localServer?.close();
    this.localServer = null;
  });
  return urlObject.toJSON();
}


================================================
FILE: driver/lib/sessions/isolate_socket.ts
================================================
import {Client} from 'rpc-websockets';

interface ExecuteArgs {
  command: string;
  [key: string]: any;
}

export class IsolateSocket extends Client {
  public isolateId: number | string = 0;
  public async executeSocketCommand(args: ExecuteArgs) {
    // call an RPC method with parameters
    return this.call(`ext.flutter.driver`, {
      ...args,
      isolateId: this.isolateId,
    }) as Promise<{
      isError: boolean;
      response: any;
    }>;
  }
}


================================================
FILE: driver/lib/sessions/log-monitor.ts
================================================
import type {EventEmitter} from 'node:events';
import {retryInterval} from 'asyncbox';
export interface LogEntry {
  timestamp: number;
  level: string;
  message: string;
}

const DEFAULT_MAX_RETRY_COUNT = 10;
const DEFAULT_BACKOFF_TIME_MS = 3000;

export type Filter = (x: LogEntry) => Promise<boolean>;

export class LogMonitor {
  private readonly _logsEmitter: EventEmitter;
  private readonly _filter: Filter;
  private _lastMatch: LogEntry | null;
  private _outputListener: ((logEntry: LogEntry) => any) | null;

  constructor(logsEmitter: EventEmitter, filter: Filter) {
    this._logsEmitter = logsEmitter;
    this._outputListener = null;
    this._filter = filter;
    this._lastMatch = null;
  }

  get started(): boolean {
    return Boolean(this._outputListener);
  }

  clearlastMatch() {
    this._lastMatch = null;
  }

  get lastMatch(): LogEntry | null {
    return this._lastMatch;
  }

  async waitForLastMatchExist(
    maxRetryCount: number = DEFAULT_MAX_RETRY_COUNT,
    retryBackoffTime: number = DEFAULT_BACKOFF_TIME_MS,
  ): Promise<LogEntry | null> {
    return await retryInterval(maxRetryCount, retryBackoffTime, async () => {
      if (this._lastMatch !== null) {
        return this._lastMatch;
      }
      throw new Error(
        `No matched log found with ${retryBackoffTime} ms interval ` +
          `up to ${maxRetryCount} times. Increasing appium:retryBackoffTime ` +
          `and appium:maxRetryCount would help.`,
      );
    });
  }

  start(): this {
    if (this.started) {
      return this;
    }

    this._outputListener = this._onOutput.bind(this);
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    this._logsEmitter.on('output', this._outputListener!);
    return this;
  }

  stop(): this {
    if (!this.started) {
      return this;
    }

    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    this._logsEmitter.off('output', this._outputListener!);
    this._outputListener = null;
    return this;
  }

  private async _onOutput(logEntry: LogEntry): Promise<void> {
    if (await this._filter(logEntry)) {
      this._lastMatch = logEntry;
    }
  }
}


================================================
FILE: driver/lib/sessions/observatory.ts
================================================
import {URL} from 'node:url';
import _ from 'lodash';
import type {FlutterDriver} from '../driver';
import {IsolateSocket} from './isolate_socket';
import {decode} from './base64url';
import type {LogEntry} from './log-monitor';
import {retryInterval} from 'asyncbox';

const truncateLength = 500;
// https://github.com/flutter/flutter/blob/f90b019c68edf4541a4c8273865a2b40c2c01eb3/dev/devicelab/lib/framework/runner.dart#L183
//  e.g. 'Observatory listening on http://127.0.0.1:52817/_w_SwaKs9-g=/'
// https://github.com/flutter/flutter/blob/52ae102f182afaa0524d0d01d21b2d86d15a11dc/packages/flutter_tools/lib/src/resident_runner.dart#L1386-L1389
//  e.g. 'An Observatory debugger and profiler on ${device.device.name} is available at: http://127.0.0.1:52817/_w_SwaKs9-g=/'
export const OBSERVATORY_URL_PATTERN = new RegExp(
  `(Observatory listening on |` +
    `An Observatory debugger and profiler on\\s.+\\sis available at: |` +
    `The Dart VM service is listening on )` +
    `((http|//)[a-zA-Z0-9:/=_\\-.\\[\\]]+)`,
);

const moduleCheckIntervalCount = 30;
const moduleCheckIntervalMs = 500;

// SOCKETS
export async function connectSocket(
  this: FlutterDriver,
  dartObservatoryURL: string,
  caps: Record<string, any>,
): Promise<IsolateSocket> {
  const isolateId = caps.isolateId;

  this.log.debug(`Establishing a connection to the Dart Observatory`);

  const connectedPromise = new Promise<IsolateSocket | null>((resolve) => {
    const socket = new IsolateSocket(dartObservatoryURL);

    const removeListenerAndResolve = (r: IsolateSocket | null) => {
      socket.removeListener(`error`, onErrorListener);
      socket.removeListener(`timeout`, onTimeoutListener);
      socket.removeListener(`open`, onOpenListener);
      resolve(r);
    };

    // Add an 'error' event handler for the client socket
    const onErrorListener = (ex: Error) => {
      this.log.error(`Connection to ${dartObservatoryURL} got an error: ${ex.message}`);
      removeListenerAndResolve(null);
    };
    socket.on(`error`, onErrorListener);
    // Add a 'close' event handler for the client socket
    socket.on(`close`, () => {
      this.log.info(`Connection to ${dartObservatoryURL} closed`);
      // @todo do we need to set this.socket = null?
    });
    // Add a 'timeout' event handler for the client socket
    const onTimeoutListener = () => {
      this.log.error(`Connection to ${dartObservatoryURL} timed out`);
      removeListenerAndResolve(null);
    };
    socket.on(`timeout`, onTimeoutListener);
    const onOpenListener = async () => {
      const originalSocketCall = socket.call;
      socket.call = async (...args: any) => {
        try {
          // `await` is needed so that rejected promise will be thrown and caught
          return await originalSocketCall.apply(socket, args);
        } catch (e) {
          this.log.errorWithException(new Error(JSON.stringify(e)));
        }
      };
      this.log.info(`Connecting to Dart Observatory: ${dartObservatoryURL}`);

      if (isolateId) {
        this.log.info(`Listing the given isolate id: ${isolateId}`);
        socket.isolateId = isolateId;
      } else {
        const vm = (await socket.call(`getVM`)) as {
          isolates: [
            {
              name: string;
              id: number;
            },
          ];
        };
        this.log.info(`Listing all isolates: ${JSON.stringify(vm.isolates)}`);
        // To accept 'main.dart:main()' and 'main'
        const mainIsolateData = vm.isolates.find((e) => e.name.includes(`main`));
        if (!mainIsolateData) {
          this.log.error(`Cannot get Dart main isolate info`);
          removeListenerAndResolve(null);
          socket.close();
          return;
        }
        // e.g. 'isolates/2978358234363215', '2978358234363215'
        socket.isolateId = mainIsolateData.id;
      }

      // It could take time to load the expected module.
      try {
        await retryInterval(moduleCheckIntervalCount, moduleCheckIntervalMs, async () => {
          const isolate = (await socket.call(`getIsolate`, {
            isolateId: `${socket.isolateId}`,
          })) as {
            extensionRPCs: [string] | null;
          } | null;
          if (!isolate) {
            throw new Error(`Cannot get main Dart Isolate`);
          }
          if (!Array.isArray(isolate.extensionRPCs)) {
            throw new Error(
              `Cannot get Dart extensionRPCs from isolate ${JSON.stringify(isolate)}`,
            );
          }
          if (isolate.extensionRPCs.indexOf(`ext.flutter.driver`) < 0) {
            throw new Error(
              `"ext.flutter.driver" is not found in "extensionRPCs" ${JSON.stringify(isolate.extensionRPCs)}`,
            );
          }
        });
      } catch (e) {
        this.log.error(e.message);
        removeListenerAndResolve(null);
        return;
      }
      removeListenerAndResolve(socket);
    };
    socket.on(`open`, onOpenListener);
  });

  const connectedSocket = await connectedPromise;
  if (connectedSocket) {
    return connectedSocket;
  }

  throw new Error(
    `Cannot connect to the Dart Observatory URL ${dartObservatoryURL}. ` +
      `Check the server log for more details`,
  );
}

export async function executeGetIsolateCommand(this: FlutterDriver, isolateId: string | number) {
  this.log.debug(`>>> getIsolate`);
  const isolate = await (this.socket as IsolateSocket).call(`getIsolate`, {
    isolateId: `${isolateId}`,
  });
  this.log.debug(`<<< ${_.truncate(JSON.stringify(isolate), {length: truncateLength})}`);
  return isolate;
}

export async function executeGetVMCommand(this: FlutterDriver) {
  this.log.debug(`>>> getVM`);
  const vm = (await (this.socket as IsolateSocket).call(`getVM`)) as {
    isolates: [
      {
        name: string;
        id: number;
      },
    ];
  };
  this.log.debug(`<<< ${_.truncate(JSON.stringify(vm), {length: truncateLength})}`);
  return vm;
}

export async function executeElementCommand(
  this: FlutterDriver,
  command: string,
  elementBase64?: string,
  extraArgs = {},
) {
  const elementObject = elementBase64 ? JSON.parse(decode(elementBase64)) : {};
  const serializedCommand = {command, ...elementObject, ...extraArgs};
  this.log.debug(`>>> ${JSON.stringify(serializedCommand)}`);
  const data = await (this.socket as IsolateSocket).executeSocketCommand(serializedCommand);
  this.log.debug(`<<< ${JSON.stringify(data)} | previous command ${command}`);
  if (data.isError) {
    throw new Error(
      `Cannot execute command ${command}, server response ${JSON.stringify(data, null, 2)}`,
    );
  }
  return data.response;
}

export function extractObservatoryUrl(logEntry: LogEntry): URL | null {
  const match = logEntry.message.match(OBSERVATORY_URL_PATTERN);
  if (!match) {
    return null;
  }

  try {
    const result = new URL(match[2]);
    result.protocol = `ws`;
    result.pathname += `ws`;
    return result;
  } catch {
    return null;
  }
}


================================================
FILE: driver/lib/sessions/session.ts
================================================
import type {FlutterDriver} from '../driver';
import _ from 'lodash';
import {startAndroidSession, connectAndroidSession} from './android';
import {startIOSSession, connectIOSSession} from './ios';
import {PLATFORM} from '../platform';
import type {XCUITestDriver} from 'appium-xcuitest-driver';
import type {AndroidUiautomator2Driver} from 'appium-uiautomator2-driver';

export const reConnectFlutterDriver = async function (
  this: FlutterDriver,
  caps: Record<string, any>,
) {
  // setup proxies - if platformName is not empty, make it less case sensitive
  if (!caps.platformName) {
    this.log.errorWithException(new Error(`No platformName was given`));
  }

  switch (_.toLower(caps.platformName)) {
    case PLATFORM.IOS:
      this.socket = await connectIOSSession.bind(this)(this.proxydriver, caps, true);
      break;
    case PLATFORM.ANDROID:
      this.socket = await connectAndroidSession.bind(this)(this.proxydriver, caps, true);
      break;
    default:
      this.log.errorWithException(
        new Error(
          `Unsupported platformName: ${caps.platformName}. ` +
            `Only the following platforms are supported: ${_.keys(PLATFORM)}`,
        ),
      );
  }
};

export const createSession: any = async function (
  this: FlutterDriver,
  sessionId: string,
  caps,
  ...args
) {
  try {
    // setup proxies - if platformName is not empty, make it less case sensitive
    switch (_.toLower(caps.platformName)) {
      case PLATFORM.IOS:
        [this.proxydriver, this.socket] = await startIOSSession.bind(this)(caps, ...args);
        (this.proxydriver as XCUITestDriver).relaxedSecurityEnabled = this.relaxedSecurityEnabled;
        (this.proxydriver as XCUITestDriver).denyInsecure = this.denyInsecure;
        (this.proxydriver as XCUITestDriver).allowInsecure = this.allowInsecure;

        break;
      case PLATFORM.ANDROID:
        [this.proxydriver, this.socket] = await startAndroidSession.bind(this)(caps, ...args);
        (this.proxydriver as AndroidUiautomator2Driver).relaxedSecurityEnabled =
          this.relaxedSecurityEnabled;
        (this.proxydriver as AndroidUiautomator2Driver).denyInsecure = this.denyInsecure;
        (this.proxydriver as AndroidUiautomator2Driver).allowInsecure = this.allowInsecure;
        break;
      default:
        this.log.errorWithException(
          new Error(
            `Unsupported platformName: ${caps.platformName}. ` +
              `Only the following platforms are supported: ${_.keys(PLATFORM)}`,
          ),
        );
    }

    return [sessionId, this.opts];
  } catch (e) {
    await this.deleteSession();
    throw e;
  }
};


================================================
FILE: driver/npm-shrinkwrap.json
================================================
{
  "name": "appium-flutter-driver",
  "version": "3.6.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "appium-flutter-driver",
      "version": "3.6.0",
      "license": "MIT",
      "dependencies": {
        "appium-android-driver": "^13.0.0",
        "appium-flutter-finder": "^0.2.0",
        "appium-ios-device": "^3.0.0",
        "appium-uiautomator2-driver": "^7.0.0",
        "appium-xcuitest-driver": "^10.0.0",
        "asyncbox": "^6.0.1",
        "bluebird": "^3.1.1",
        "lodash": "^4.0.0",
        "portscanner": "^2.2.0",
        "rpc-websockets": "^10.0.0"
      },
      "devDependencies": {
        "@appium/eslint-config-appium-ts": "^3.0.0",
        "@appium/tsconfig": "^1.0.0",
        "@appium/types": "^1.0.0",
        "@semantic-release/changelog": "^6.0.3",
        "@semantic-release/git": "^10.0.1",
        "prettier": "^3.0.0",
        "semantic-release": "^25.0.2",
        "ts-node": "^10.9.1",
        "typescript": "~5.9"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      },
      "peerDependencies": {
        "appium": "^3.0.0"
      }
    },
    "node_modules/@appium/base-driver": {
      "version": "10.3.0",
      "resolved": "https://registry.npmjs.org/@appium/base-driver/-/base-driver-10.3.0.tgz",
      "integrity": "sha512-9r+1f9EtcJt9NXIlyHdFMoD7DsAZPzpRq4Kj1hGZf7+26q1SFEyyMAuWxvlWGY7EoyPtPRyMUkM4dHiSK3Q9+w==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/support": "7.1.0",
        "@appium/types": "1.3.0",
        "@colors/colors": "1.6.0",
        "async-lock": "1.4.1",
        "asyncbox": "6.1.0",
        "axios": "1.15.0",
        "bluebird": "3.7.2",
        "body-parser": "2.2.2",
        "express": "5.2.1",
        "fastest-levenshtein": "1.0.16",
        "http-status-codes": "2.3.0",
        "lodash": "4.18.1",
        "lru-cache": "11.3.3",
        "method-override": "3.0.0",
        "morgan": "1.10.1",
        "path-to-regexp": "8.4.2",
        "serve-favicon": "2.5.1",
        "type-fest": "5.5.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      },
      "optionalDependencies": {
        "spdy": "4.0.2"
      }
    },
    "node_modules/@appium/logger": {
      "version": "2.0.6",
      "resolved": "https://registry.npmjs.org/@appium/logger/-/logger-2.0.6.tgz",
      "integrity": "sha512-9e8n9CtINBwi1ASEU5OyswmR2F7OnbrGfmf9yTy9i+rx4GR9RJlEp0/arsxvuyWCep67tOmM4FiRyXxxHjOK5Q==",
      "license": "ISC",
      "dependencies": {
        "console-control-strings": "1.1.0",
        "lodash": "4.18.1",
        "lru-cache": "11.3.3",
        "set-blocking": "2.0.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/@appium/schema": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/@appium/schema/-/schema-1.1.0.tgz",
      "integrity": "sha512-m0vTLU7mhC9RR294Nz84g+FhEQ0iZKq6p3rfz1+qfEqCXRXUvDbllSOu2tCVpBKMIoEFZAmkwjuwXobJpCnilQ==",
      "license": "Apache-2.0",
      "dependencies": {
        "json-schema": "0.4.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/@appium/support": {
      "version": "7.1.0",
      "resolved": "https://registry.npmjs.org/@appium/support/-/support-7.1.0.tgz",
      "integrity": "sha512-kY4Qv4TzLCYmZnN2eNptEa8RiRzpbimIQ6tKuDaqLC2Y3q5Al4NumL/xRQAvfXJq/hNezq2Jh8NwciEW8zX/0g==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/logger": "2.0.6",
        "@appium/tsconfig": "1.1.2",
        "@appium/types": "1.3.0",
        "@colors/colors": "1.6.0",
        "archiver": "7.0.1",
        "asyncbox": "6.1.0",
        "axios": "1.15.0",
        "base64-stream": "1.0.0",
        "bluebird": "3.7.2",
        "bplist-creator": "0.1.1",
        "bplist-parser": "0.3.2",
        "form-data": "4.0.5",
        "get-stream": "9.0.1",
        "glob": "13.0.6",
        "jsftp": "2.1.3",
        "klaw": "4.1.0",
        "lockfile": "1.0.4",
        "lodash": "4.18.1",
        "log-symbols": "7.0.1",
        "ncp": "2.0.0",
        "package-directory": "8.2.0",
        "plist": "3.1.0",
        "pluralize": "8.0.0",
        "read-pkg": "10.1.0",
        "resolve-from": "5.0.0",
        "sanitize-filename": "1.6.4",
        "semver": "7.7.4",
        "shell-quote": "1.8.3",
        "supports-color": "10.2.2",
        "teen_process": "4.1.0",
        "type-fest": "5.5.0",
        "uuid": "13.0.0",
        "which": "6.0.1",
        "yauzl": "3.3.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      },
      "optionalDependencies": {
        "sharp": "0.34.5"
      }
    },
    "node_modules/@appium/tsconfig": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/@appium/tsconfig/-/tsconfig-1.1.2.tgz",
      "integrity": "sha512-lHKBm7hXCROc1Ha/cBxS4o3iQkeY96Pz7qM9Uh9vFDkdpTGBk56V1lmc3iGcgBYKBlaRT/LZmTsqClvHoiXhvw==",
      "license": "Apache-2.0",
      "dependencies": {
        "@tsconfig/node20": "20.1.9"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/@appium/types": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/@appium/types/-/types-1.3.0.tgz",
      "integrity": "sha512-Gv4ev/5K5N7TvAHqem2DmB50zipC951QlmCDpuxDNHQl2dtCr20vJgnN8if7upqLcBX/6yNp3udR+f1n99zgcQ==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/logger": "2.0.6",
        "@appium/schema": "1.1.0",
        "@appium/tsconfig": "1.1.2",
        "type-fest": "5.5.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/@babel/code-frame": {
      "version": "7.29.0",
      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
      "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
      "license": "MIT",
      "dependencies": {
        "@babel/helper-validator-identifier": "^7.28.5",
        "js-tokens": "^4.0.0",
        "picocolors": "^1.1.1"
      },
      "engines": {
        "node": ">=6.9.0"
      }
    },
    "node_modules/@babel/helper-validator-identifier": {
      "version": "7.28.5",
      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
      "license": "MIT",
      "engines": {
        "node": ">=6.9.0"
      }
    },
    "node_modules/@colors/colors": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
      "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
      "license": "MIT",
      "engines": {
        "node": ">=0.1.90"
      }
    },
    "node_modules/@img/colour": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
      "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@img/sharp-darwin-arm64": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
      "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
      "cpu": [
        "arm64"
      ],
      "license": "Apache-2.0",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-libvips-darwin-arm64": "1.2.4"
      }
    },
    "node_modules/@img/sharp-libvips-darwin-arm64": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
      "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
      "cpu": [
        "arm64"
      ],
      "license": "LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "darwin"
      ],
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@isaacs/cliui": {
      "version": "8.0.2",
      "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
      "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
      "license": "ISC",
      "dependencies": {
        "string-width": "^5.1.2",
        "string-width-cjs": "npm:string-width@^4.2.0",
        "strip-ansi": "^7.0.1",
        "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
        "wrap-ansi": "^8.1.0",
        "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
      },
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
      "version": "6.2.3",
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
      "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
      }
    },
    "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
      "version": "9.2.2",
      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
      "license": "MIT"
    },
    "node_modules/@isaacs/cliui/node_modules/string-width": {
      "version": "5.1.2",
      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
      "license": "MIT",
      "dependencies": {
        "eastasianwidth": "^0.2.0",
        "emoji-regex": "^9.2.2",
        "strip-ansi": "^7.0.1"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
      "version": "7.2.0",
      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
      "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
      "license": "MIT",
      "dependencies": {
        "ansi-regex": "^6.2.2"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
      }
    },
    "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
      "version": "8.1.0",
      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
      "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
      "license": "MIT",
      "dependencies": {
        "ansi-styles": "^6.1.0",
        "string-width": "^5.0.1",
        "strip-ansi": "^7.0.1"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
      }
    },
    "node_modules/@pkgjs/parseargs": {
      "version": "0.11.0",
      "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
      "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">=14"
      }
    },
    "node_modules/@sec-ant/readable-stream": {
      "version": "0.4.1",
      "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
      "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
      "license": "MIT"
    },
    "node_modules/@swc/helpers": {
      "version": "0.5.21",
      "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz",
      "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==",
      "license": "Apache-2.0",
      "dependencies": {
        "tslib": "^2.8.0"
      }
    },
    "node_modules/@tsconfig/node20": {
      "version": "20.1.9",
      "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.9.tgz",
      "integrity": "sha512-IjlTv1RsvnPtUcjTqtVsZExKVq+KQx4g5pCP5tI7rAs6Xesl2qFwSz/tPDBC4JajkL/MlezBu3gPUwqRHl+RIg==",
      "license": "MIT"
    },
    "node_modules/@types/node": {
      "version": "25.6.0",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
      "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
      "license": "MIT",
      "dependencies": {
        "undici-types": "~7.19.0"
      }
    },
    "node_modules/@types/normalize-package-data": {
      "version": "2.4.4",
      "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
      "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
      "license": "MIT"
    },
    "node_modules/@types/uuid": {
      "version": "8.3.4",
      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
      "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
      "license": "MIT"
    },
    "node_modules/@types/ws": {
      "version": "8.18.1",
      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
      "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
      "license": "MIT",
      "dependencies": {
        "@types/node": "*"
      }
    },
    "node_modules/@xmldom/xmldom": {
      "version": "0.9.9",
      "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.9.tgz",
      "integrity": "sha512-qycIHAucxy/LXAYIjmLmtQ8q9GPnMbnjG1KXhWm9o5sCr6pOYDATkMPiTNa6/v8eELyqOQ2FsEqeoFYmgv/gJg==",
      "license": "MIT",
      "engines": {
        "node": ">=14.6"
      }
    },
    "node_modules/abort-controller": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
      "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
      "license": "MIT",
      "dependencies": {
        "event-target-shim": "^5.0.0"
      },
      "engines": {
        "node": ">=6.5"
      }
    },
    "node_modules/accepts": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
      "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
      "license": "MIT",
      "dependencies": {
        "mime-types": "^3.0.0",
        "negotiator": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/ansi-regex": {
      "version": "6.2.2",
      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
      "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
      }
    },
    "node_modules/ansi-styles": {
      "version": "4.3.0",
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
      "license": "MIT",
      "dependencies": {
        "color-convert": "^2.0.1"
      },
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
      }
    },
    "node_modules/appium-adb": {
      "version": "14.3.2",
      "resolved": "https://registry.npmjs.org/appium-adb/-/appium-adb-14.3.2.tgz",
      "integrity": "sha512-V3YLPzHHG8vjw8oQ7d7sR6OvbadLIYwv5WYji3/YBrAe0NTRzhzj07e/m3XML+ipDOweZhKBebAclavdgkJWWg==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/support": "^7.0.0-rc.1",
        "async-lock": "^1.0.0",
        "asyncbox": "^6.0.1",
        "bluebird": "^3.4.7",
        "ini": "^6.0.0",
        "lodash": "^4.0.0",
        "lru-cache": "^11.1.0",
        "semver": "^7.0.0",
        "teen_process": "^4.0.4"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/appium-android-driver": {
      "version": "13.1.2",
      "resolved": "https://registry.npmjs.org/appium-android-driver/-/appium-android-driver-13.1.2.tgz",
      "integrity": "sha512-ZzzJvGjxhGSNEwgK3DS8zs5yXAQ9u7n+vRV25dYN2Dt1ORrvsbEBnKhv9+5a/FeV9ivKg47J6r2kDV8DcDL8LQ==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/support": "^7.0.0-rc.1",
        "@colors/colors": "^1.6.0",
        "appium-adb": "^14.3.0",
        "appium-chromedriver": "^8.2.25",
        "asyncbox": "^6.1.0",
        "axios": "^1.x",
        "bluebird": "^3.4.7",
        "io.appium.settings": "^7.0.4",
        "lodash": "^4.17.4",
        "lru-cache": "^11.1.0",
        "moment": "^2.24.0",
        "moment-timezone": "^0.x",
        "portscanner": "^2.2.0",
        "semver": "^7.0.0",
        "teen_process": "^4.0.7",
        "ws": "^8.0.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      },
      "peerDependencies": {
        "appium": "^3.0.0-rc.2"
      }
    },
    "node_modules/appium-chromedriver": {
      "version": "8.2.26",
      "resolved": "https://registry.npmjs.org/appium-chromedriver/-/appium-chromedriver-8.2.26.tgz",
      "integrity": "sha512-/H+N52PPmv5j7FJnOHIdtaKeKxAXdo9SOB1QCXnGb4Qi9dDc5KupJZFykKuy03/c9uaHqyga3wrT+rmkRl5Nug==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/base-driver": "^10.0.0-rc.2",
        "@appium/support": "^7.0.0-rc.1",
        "@xmldom/xmldom": "^0.x",
        "appium-adb": "^14.0.0",
        "asyncbox": "^6.0.1",
        "axios": "^1.6.5",
        "bluebird": "^3.5.1",
        "compare-versions": "^6.0.0",
        "lodash": "^4.17.4",
        "semver": "^7.0.0",
        "teen_process": "^4.0.4",
        "xpath": "^0.x"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/appium-flutter-finder": {
      "version": "0.2.0",
      "resolved": "https://registry.npmjs.org/appium-flutter-finder/-/appium-flutter-finder-0.2.0.tgz",
      "integrity": "sha512-klwh74phzhU6OhSf/arHVHP7AF6fyQGZ2GgzX/wO+gPKIS+rNWOQgizPPimki3H4goB7wB0TaicamN/DCqLmRA==",
      "license": "MIT"
    },
    "node_modules/appium-ios-device": {
      "version": "3.1.10",
      "resolved": "https://registry.npmjs.org/appium-ios-device/-/appium-ios-device-3.1.10.tgz",
      "integrity": "sha512-2oE7yQtLSdrcZ9YArqgGguzDuiplHj0GXSMlTfwTXl0n22DEzkV0M1mXdaNaWNuzVBJ5VDc1EuYv38p1ruuk2g==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/support": "^7.0.0-rc.1",
        "asyncbox": "^6.0.1",
        "axios": "^1.6.7",
        "bluebird": "^3.1.1",
        "bplist-creator": "^0.x",
        "bplist-parser": "^0.x",
        "lodash": "^4.17.15",
        "semver": "^7.0.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/appium-uiautomator2-driver": {
      "version": "7.1.2",
      "resolved": "https://registry.npmjs.org/appium-uiautomator2-driver/-/appium-uiautomator2-driver-7.1.2.tgz",
      "integrity": "sha512-exm8loSaNFh61DMTf/BeFL/J6xY6w+Ta24g8exKEchowCWpm4zimGC4y8BRoP6+1iQ+R88x4fMIvAx8Uf9M7bQ==",
      "hasShrinkwrap": true,
      "license": "Apache-2.0",
      "dependencies": {
        "appium-adb": "^14.0.0",
        "appium-android-driver": "^13.1.1",
        "appium-uiautomator2-server": "^9.11.1",
        "asyncbox": "^6.0.1",
        "axios": "^1.13.5",
        "bluebird": "^3.5.1",
        "css-selector-parser": "^3.0.0",
        "io.appium.settings": "^7.0.1",
        "lodash": "^4.17.4",
        "portscanner": "^2.2.0",
        "teen_process": "^4.0.4"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      },
      "peerDependencies": {
        "appium": "^3.0.0-rc.2"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@appium/base-driver": {
      "version": "10.3.0",
      "resolved": "https://registry.npmjs.org/@appium/base-driver/-/base-driver-10.3.0.tgz",
      "integrity": "sha512-9r+1f9EtcJt9NXIlyHdFMoD7DsAZPzpRq4Kj1hGZf7+26q1SFEyyMAuWxvlWGY7EoyPtPRyMUkM4dHiSK3Q9+w==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/support": "7.1.0",
        "@appium/types": "1.3.0",
        "@colors/colors": "1.6.0",
        "async-lock": "1.4.1",
        "asyncbox": "6.1.0",
        "axios": "1.15.0",
        "bluebird": "3.7.2",
        "body-parser": "2.2.2",
        "express": "5.2.1",
        "fastest-levenshtein": "1.0.16",
        "http-status-codes": "2.3.0",
        "lodash": "4.18.1",
        "lru-cache": "11.3.3",
        "method-override": "3.0.0",
        "morgan": "1.10.1",
        "path-to-regexp": "8.4.2",
        "serve-favicon": "2.5.1",
        "type-fest": "5.5.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      },
      "optionalDependencies": {
        "spdy": "4.0.2"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@appium/logger": {
      "version": "2.0.6",
      "resolved": "https://registry.npmjs.org/@appium/logger/-/logger-2.0.6.tgz",
      "integrity": "sha512-9e8n9CtINBwi1ASEU5OyswmR2F7OnbrGfmf9yTy9i+rx4GR9RJlEp0/arsxvuyWCep67tOmM4FiRyXxxHjOK5Q==",
      "license": "ISC",
      "dependencies": {
        "console-control-strings": "1.1.0",
        "lodash": "4.18.1",
        "lru-cache": "11.3.3",
        "set-blocking": "2.0.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@appium/schema": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/@appium/schema/-/schema-1.1.0.tgz",
      "integrity": "sha512-m0vTLU7mhC9RR294Nz84g+FhEQ0iZKq6p3rfz1+qfEqCXRXUvDbllSOu2tCVpBKMIoEFZAmkwjuwXobJpCnilQ==",
      "license": "Apache-2.0",
      "dependencies": {
        "json-schema": "0.4.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@appium/support": {
      "version": "7.1.0",
      "resolved": "https://registry.npmjs.org/@appium/support/-/support-7.1.0.tgz",
      "integrity": "sha512-kY4Qv4TzLCYmZnN2eNptEa8RiRzpbimIQ6tKuDaqLC2Y3q5Al4NumL/xRQAvfXJq/hNezq2Jh8NwciEW8zX/0g==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/logger": "2.0.6",
        "@appium/tsconfig": "1.1.2",
        "@appium/types": "1.3.0",
        "@colors/colors": "1.6.0",
        "archiver": "7.0.1",
        "asyncbox": "6.1.0",
        "axios": "1.15.0",
        "base64-stream": "1.0.0",
        "bluebird": "3.7.2",
        "bplist-creator": "0.1.1",
        "bplist-parser": "0.3.2",
        "form-data": "4.0.5",
        "get-stream": "9.0.1",
        "glob": "13.0.6",
        "jsftp": "2.1.3",
        "klaw": "4.1.0",
        "lockfile": "1.0.4",
        "lodash": "4.18.1",
        "log-symbols": "7.0.1",
        "ncp": "2.0.0",
        "package-directory": "8.2.0",
        "plist": "3.1.0",
        "pluralize": "8.0.0",
        "read-pkg": "10.1.0",
        "resolve-from": "5.0.0",
        "sanitize-filename": "1.6.4",
        "semver": "7.7.4",
        "shell-quote": "1.8.3",
        "supports-color": "10.2.2",
        "teen_process": "4.1.0",
        "type-fest": "5.5.0",
        "uuid": "13.0.0",
        "which": "6.0.1",
        "yauzl": "3.3.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      },
      "optionalDependencies": {
        "sharp": "0.34.5"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@appium/tsconfig": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/@appium/tsconfig/-/tsconfig-1.1.2.tgz",
      "integrity": "sha512-lHKBm7hXCROc1Ha/cBxS4o3iQkeY96Pz7qM9Uh9vFDkdpTGBk56V1lmc3iGcgBYKBlaRT/LZmTsqClvHoiXhvw==",
      "license": "Apache-2.0",
      "dependencies": {
        "@tsconfig/node20": "20.1.9"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@appium/types": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/@appium/types/-/types-1.3.0.tgz",
      "integrity": "sha512-Gv4ev/5K5N7TvAHqem2DmB50zipC951QlmCDpuxDNHQl2dtCr20vJgnN8if7upqLcBX/6yNp3udR+f1n99zgcQ==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/logger": "2.0.6",
        "@appium/schema": "1.1.0",
        "@appium/tsconfig": "1.1.2",
        "type-fest": "5.5.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@babel/code-frame": {
      "version": "7.29.0",
      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
      "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
      "license": "MIT",
      "dependencies": {
        "@babel/helper-validator-identifier": "^7.28.5",
        "js-tokens": "^4.0.0",
        "picocolors": "^1.1.1"
      },
      "engines": {
        "node": ">=6.9.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@babel/helper-validator-identifier": {
      "version": "7.28.5",
      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
      "license": "MIT",
      "engines": {
        "node": ">=6.9.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@colors/colors": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
      "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
      "license": "MIT",
      "engines": {
        "node": ">=0.1.90"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@img/colour": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
      "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@isaacs/cliui": {
      "version": "8.0.2",
      "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
      "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
      "license": "ISC",
      "dependencies": {
        "string-width": "^5.1.2",
        "string-width-cjs": "npm:string-width@^4.2.0",
        "strip-ansi": "^7.0.1",
        "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
        "wrap-ansi": "^8.1.0",
        "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
      },
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@isaacs/cliui/node_modules/ansi-styles": {
      "version": "6.2.3",
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
      "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@isaacs/cliui/node_modules/emoji-regex": {
      "version": "9.2.2",
      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@isaacs/cliui/node_modules/string-width": {
      "version": "5.1.2",
      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
      "license": "MIT",
      "dependencies": {
        "eastasianwidth": "^0.2.0",
        "emoji-regex": "^9.2.2",
        "strip-ansi": "^7.0.1"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@isaacs/cliui/node_modules/strip-ansi": {
      "version": "7.2.0",
      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
      "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
      "license": "MIT",
      "dependencies": {
        "ansi-regex": "^6.2.2"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
      "version": "8.1.0",
      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
      "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
      "license": "MIT",
      "dependencies": {
        "ansi-styles": "^6.1.0",
        "string-width": "^5.0.1",
        "strip-ansi": "^7.0.1"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@pkgjs/parseargs": {
      "version": "0.11.0",
      "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
      "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">=14"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@sec-ant/readable-stream": {
      "version": "0.4.1",
      "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
      "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@tsconfig/node20": {
      "version": "20.1.9",
      "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.9.tgz",
      "integrity": "sha512-IjlTv1RsvnPtUcjTqtVsZExKVq+KQx4g5pCP5tI7rAs6Xesl2qFwSz/tPDBC4JajkL/MlezBu3gPUwqRHl+RIg==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@types/normalize-package-data": {
      "version": "2.4.4",
      "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
      "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/@xmldom/xmldom": {
      "version": "0.9.9",
      "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.9.tgz",
      "integrity": "sha512-qycIHAucxy/LXAYIjmLmtQ8q9GPnMbnjG1KXhWm9o5sCr6pOYDATkMPiTNa6/v8eELyqOQ2FsEqeoFYmgv/gJg==",
      "license": "MIT",
      "engines": {
        "node": ">=14.6"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/abort-controller": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
      "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
      "license": "MIT",
      "dependencies": {
        "event-target-shim": "^5.0.0"
      },
      "engines": {
        "node": ">=6.5"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/accepts": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
      "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
      "license": "MIT",
      "dependencies": {
        "mime-types": "^3.0.0",
        "negotiator": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/ansi-regex": {
      "version": "6.2.2",
      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
      "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/ansi-styles": {
      "version": "4.3.0",
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
      "license": "MIT",
      "dependencies": {
        "color-convert": "^2.0.1"
      },
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/appium-adb": {
      "version": "14.3.1",
      "resolved": "https://registry.npmjs.org/appium-adb/-/appium-adb-14.3.1.tgz",
      "integrity": "sha512-Pv9Ti0jji0Q0QewBJfjPiECV5IO6ex5qK6YugZknxxjJ5QadLDjt/FW6z4/THGAQJdbYriwqrxTqLxB6+DwqBA==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/support": "^7.0.0-rc.1",
        "async-lock": "^1.0.0",
        "asyncbox": "^6.0.1",
        "bluebird": "^3.4.7",
        "ini": "^6.0.0",
        "lodash": "^4.0.0",
        "lru-cache": "^11.1.0",
        "semver": "^7.0.0",
        "teen_process": "^4.0.4"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/appium-android-driver": {
      "version": "13.1.1",
      "resolved": "https://registry.npmjs.org/appium-android-driver/-/appium-android-driver-13.1.1.tgz",
      "integrity": "sha512-UZQmkm5YELkuqLE7Rz6wCpXxKhK8WQeDQsFt/9gkOQLgK2yaQdfAj8OM2mP8Gmru3iJFep39VGM2EWYAL2U9Qw==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/support": "^7.0.0-rc.1",
        "@colors/colors": "^1.6.0",
        "appium-adb": "^14.3.0",
        "appium-chromedriver": "^8.2.25",
        "asyncbox": "^6.1.0",
        "axios": "^1.x",
        "bluebird": "^3.4.7",
        "io.appium.settings": "^7.0.4",
        "lodash": "^4.17.4",
        "lru-cache": "^11.1.0",
        "moment": "^2.24.0",
        "moment-timezone": "^0.x",
        "portscanner": "^2.2.0",
        "semver": "^7.0.0",
        "teen_process": "^4.0.7",
        "ws": "^8.0.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      },
      "peerDependencies": {
        "appium": "^3.0.0-rc.2"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/appium-chromedriver": {
      "version": "8.2.25",
      "resolved": "https://registry.npmjs.org/appium-chromedriver/-/appium-chromedriver-8.2.25.tgz",
      "integrity": "sha512-fxSDBSUwS1hFoHKMneB0YWT5I0X2sGgXu8PbRLKv+CpveKiZKtomqtG+LlWo7zgJfITrJ4/fi6WCYBSpMG05PQ==",
      "license": "Apache-2.0",
      "dependencies": {
        "@appium/base-driver": "^10.0.0-rc.2",
        "@appium/support": "^7.0.0-rc.1",
        "@xmldom/xmldom": "^0.x",
        "appium-adb": "^14.0.0",
        "asyncbox": "^6.0.1",
        "axios": "^1.6.5",
        "bluebird": "^3.5.1",
        "compare-versions": "^6.0.0",
        "lodash": "^4.17.4",
        "semver": "^7.0.0",
        "teen_process": "^4.0.4",
        "xpath": "^0.x"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/appium-uiautomator2-server": {
      "version": "9.11.1",
      "resolved": "https://registry.npmjs.org/appium-uiautomator2-server/-/appium-uiautomator2-server-9.11.1.tgz",
      "integrity": "sha512-MAlnHFhUdQ/gdpzXcJlK5chuMQLjhOqeoD1gPGmsr3raAElPHdKDYzSiaxqvDFu7XRYug6JfWmBsTSfdASy/RQ==",
      "license": "Apache-2.0",
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/archiver": {
      "version": "7.0.1",
      "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz",
      "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==",
      "license": "MIT",
      "dependencies": {
        "archiver-utils": "^5.0.2",
        "async": "^3.2.4",
        "buffer-crc32": "^1.0.0",
        "readable-stream": "^4.0.0",
        "readdir-glob": "^1.1.2",
        "tar-stream": "^3.0.0",
        "zip-stream": "^6.0.1"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/archiver-utils": {
      "version": "5.0.2",
      "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz",
      "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==",
      "license": "MIT",
      "dependencies": {
        "glob": "^10.0.0",
        "graceful-fs": "^4.2.0",
        "is-stream": "^2.0.1",
        "lazystream": "^1.0.0",
        "lodash": "^4.17.15",
        "normalize-path": "^3.0.0",
        "readable-stream": "^4.0.0"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/archiver-utils/node_modules/brace-expansion": {
      "version": "2.0.3",
      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
      "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
      "license": "MIT",
      "dependencies": {
        "balanced-match": "^1.0.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/archiver-utils/node_modules/glob": {
      "version": "10.5.0",
      "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
      "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
      "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
      "license": "ISC",
      "dependencies": {
        "foreground-child": "^3.1.0",
        "jackspeak": "^3.1.2",
        "minimatch": "^9.0.4",
        "minipass": "^7.1.2",
        "package-json-from-dist": "^1.0.0",
        "path-scurry": "^1.11.1"
      },
      "bin": {
        "glob": "dist/esm/bin.mjs"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/archiver-utils/node_modules/lru-cache": {
      "version": "10.4.3",
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
      "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
      "license": "ISC"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/archiver-utils/node_modules/minimatch": {
      "version": "9.0.9",
      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
      "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
      "license": "ISC",
      "dependencies": {
        "brace-expansion": "^2.0.2"
      },
      "engines": {
        "node": ">=16 || 14 >=14.17"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/archiver-utils/node_modules/path-scurry": {
      "version": "1.11.1",
      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
      "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
      "license": "BlueOak-1.0.0",
      "dependencies": {
        "lru-cache": "^10.2.0",
        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
      },
      "engines": {
        "node": ">=16 || 14 >=14.18"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/async": {
      "version": "3.2.6",
      "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
      "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/async-lock": {
      "version": "1.4.1",
      "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz",
      "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/asyncbox": {
      "version": "6.1.0",
      "resolved": "https://registry.npmjs.org/asyncbox/-/asyncbox-6.1.0.tgz",
      "integrity": "sha512-KZwKNVnDdDe0ubN+fFMuHhSljZNHnbjdJABImoqFzQP61oIg6sMlhXIqOIu3WRd7YwW89q+eVj2Ty/Ax5dbh2Q==",
      "license": "Apache-2.0",
      "dependencies": {
        "p-limit": "^7.2.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
        "npm": ">=10"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/asynckit": {
      "version": "0.4.0",
      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/axios": {
      "version": "1.15.0",
      "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
      "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
      "license": "MIT",
      "dependencies": {
        "follow-redirects": "^1.15.11",
        "form-data": "^4.0.5",
        "proxy-from-env": "^2.1.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/b4a": {
      "version": "1.8.0",
      "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
      "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
      "license": "Apache-2.0",
      "peerDependencies": {
        "react-native-b4a": "*"
      },
      "peerDependenciesMeta": {
        "react-native-b4a": {
          "optional": true
        }
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/balanced-match": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/bare-events": {
      "version": "2.8.2",
      "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
      "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
      "license": "Apache-2.0",
      "peerDependencies": {
        "bare-abort-controller": "*"
      },
      "peerDependenciesMeta": {
        "bare-abort-controller": {
          "optional": true
        }
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/bare-fs": {
      "version": "4.7.0",
      "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.0.tgz",
      "integrity": "sha512-xzqKsCFxAek9aezYhjJuJRXBIaYlg/0OGDTZp+T8eYmYMlm66cs6cYko02drIyjN2CBbi+I6L7YfXyqpqtKRXA==",
      "license": "Apache-2.0",
      "dependencies": {
        "bare-events": "^2.5.4",
        "bare-path": "^3.0.0",
        "bare-stream": "^2.6.4",
        "bare-url": "^2.2.2",
        "fast-fifo": "^1.3.2"
      },
      "engines": {
        "bare": ">=1.16.0"
      },
      "peerDependencies": {
        "bare-buffer": "*"
      },
      "peerDependenciesMeta": {
        "bare-buffer": {
          "optional": true
        }
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/bare-os": {
      "version": "3.8.7",
      "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz",
      "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==",
      "license": "Apache-2.0",
      "engines": {
        "bare": ">=1.14.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/bare-path": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
      "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
      "license": "Apache-2.0",
      "dependencies": {
        "bare-os": "^3.0.1"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/bare-stream": {
      "version": "2.12.0",
      "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.12.0.tgz",
      "integrity": "sha512-w28i8lkBgREV3rPXGbgK+BO66q+ZpKqRWrZLiCdmmUlLPrQ45CzkvRhN+7lnv00Gpi2zy5naRxnUFAxCECDm9g==",
      "license": "Apache-2.0",
      "dependencies": {
        "streamx": "^2.25.0",
        "teex": "^1.0.1"
      },
      "peerDependencies": {
        "bare-abort-controller": "*",
        "bare-buffer": "*",
        "bare-events": "*"
      },
      "peerDependenciesMeta": {
        "bare-abort-controller": {
          "optional": true
        },
        "bare-buffer": {
          "optional": true
        },
        "bare-events": {
          "optional": true
        }
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/bare-url": {
      "version": "2.4.0",
      "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz",
      "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==",
      "license": "Apache-2.0",
      "dependencies": {
        "bare-path": "^3.0.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/base64-js": {
      "version": "1.5.1",
      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/base64-stream": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/base64-stream/-/base64-stream-1.0.0.tgz",
      "integrity": "sha512-BQQZftaO48FcE1Kof9CmXMFaAdqkcNorgc8CxesZv9nMbbTF1EFyQe89UOuh//QMmdtfUDXyO8rgUalemL5ODA==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/basic-auth": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
      "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
      "license": "MIT",
      "dependencies": {
        "safe-buffer": "5.1.2"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/big-integer": {
      "version": "1.6.52",
      "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
      "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
      "license": "Unlicense",
      "engines": {
        "node": ">=0.6"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/bluebird": {
      "version": "3.7.2",
      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/body-parser": {
      "version": "2.2.2",
      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
      "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
      "license": "MIT",
      "dependencies": {
        "bytes": "^3.1.2",
        "content-type": "^1.0.5",
        "debug": "^4.4.3",
        "http-errors": "^2.0.0",
        "iconv-lite": "^0.7.0",
        "on-finished": "^2.4.1",
        "qs": "^6.14.1",
        "raw-body": "^3.0.1",
        "type-is": "^2.0.1"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/bplist-creator": {
      "version": "0.1.1",
      "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.1.tgz",
      "integrity": "sha512-Ese7052fdWrxp/vqSJkydgx/1MdBnNOCV2XVfbmdGWD2H6EYza+Q4pyYSuVSnCUD22hfI/BFI4jHaC3NLXLlJQ==",
      "license": "MIT",
      "dependencies": {
        "stream-buffers": "2.2.x"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/bplist-parser": {
      "version": "0.3.2",
      "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz",
      "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==",
      "license": "MIT",
      "dependencies": {
        "big-integer": "1.6.x"
      },
      "engines": {
        "node": ">= 5.10.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/buffer-crc32": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
      "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
      "license": "MIT",
      "engines": {
        "node": ">=8.0.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/bytes": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/call-bind-apply-helpers": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/call-bound": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "get-intrinsic": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/color-convert": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
      "license": "MIT",
      "dependencies": {
        "color-name": "~1.1.4"
      },
      "engines": {
        "node": ">=7.0.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/color-name": {
      "version": "1.1.4",
      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/combined-stream": {
      "version": "1.0.8",
      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
      "license": "MIT",
      "dependencies": {
        "delayed-stream": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/compare-versions": {
      "version": "6.1.1",
      "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz",
      "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/compress-commons": {
      "version": "6.0.2",
      "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
      "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==",
      "license": "MIT",
      "dependencies": {
        "crc-32": "^1.2.0",
        "crc32-stream": "^6.0.0",
        "is-stream": "^2.0.1",
        "normalize-path": "^3.0.0",
        "readable-stream": "^4.0.0"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/console-control-strings": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
      "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
      "license": "ISC"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/content-disposition": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
      "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/content-type": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/cookie": {
      "version": "0.7.2",
      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/cookie-signature": {
      "version": "1.2.2",
      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
      "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
      "license": "MIT",
      "engines": {
        "node": ">=6.6.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/core-util-is": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/crc-32": {
      "version": "1.2.2",
      "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
      "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
      "license": "Apache-2.0",
      "bin": {
        "crc32": "bin/crc32.njs"
      },
      "engines": {
        "node": ">=0.8"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/crc32-stream": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz",
      "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==",
      "license": "MIT",
      "dependencies": {
        "crc-32": "^1.2.0",
        "readable-stream": "^4.0.0"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/cross-spawn": {
      "version": "7.0.6",
      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
      "license": "MIT",
      "dependencies": {
        "path-key": "^3.1.0",
        "shebang-command": "^2.0.0",
        "which": "^2.0.1"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/cross-spawn/node_modules/isexe": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
      "license": "ISC"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/cross-spawn/node_modules/which": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
      "license": "ISC",
      "dependencies": {
        "isexe": "^2.0.0"
      },
      "bin": {
        "node-which": "bin/node-which"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/css-selector-parser": {
      "version": "3.3.0",
      "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz",
      "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/mdevils"
        },
        {
          "type": "patreon",
          "url": "https://patreon.com/mdevils"
        }
      ],
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/debug": {
      "version": "4.4.3",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
      "license": "MIT",
      "dependencies": {
        "ms": "^2.1.3"
      },
      "engines": {
        "node": ">=6.0"
      },
      "peerDependenciesMeta": {
        "supports-color": {
          "optional": true
        }
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/delayed-stream": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
      "license": "MIT",
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/depd": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/detect-libc": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
      "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
      "license": "Apache-2.0",
      "optional": true,
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/detect-node": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
      "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
      "license": "MIT",
      "optional": true
    },
    "node_modules/appium-uiautomator2-driver/node_modules/dunder-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.1",
        "es-errors": "^1.3.0",
        "gopd": "^1.2.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/duplexer": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
      "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/eastasianwidth": {
      "version": "0.2.0",
      "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
      "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/ee-first": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/emoji-regex": {
      "version": "8.0.0",
      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/encodeurl": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/es-define-property": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/es-errors": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/es-object-atoms": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/es-set-tostringtag": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.6",
        "has-tostringtag": "^1.0.2",
        "hasown": "^2.0.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/escape-html": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
      "license": "MIT"
    },
    "node_modules/appium-uiautomator2-driver/node_modules/etag": {
      "version": "1.8.1",
      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/event-target-shim": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
      "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
      "license": "MIT",
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/events": {
      "version": "3.3.0",
      "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
      "license": "MIT",
      "engines": {
        "node": ">=0.8.x"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/events-universal": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
      "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
      "license": "Apache-2.0",
      "dependencies": {
        "bare-events": "^2.7.0"
      }
    },
    "node_modules/appium-uiautomator2-driver/node_modules/express": {
      "version": "5.2.1",
      "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
      "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
      "license": "MIT",
      "dependencies": {
        "accepts": "^2.0.0",
        "body-parser": "^2.2.1",
        "content-disposition": "^1.0.0",
        "content-type": "^1.0.5",
        "cookie": "^0.7.1",
        "cookie-signature": "^1.2.1",
        "debug": "^4.4.0",
        "depd": "^2.0.0",
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "etag": "^1.8.1",
        "finalhandler": "^2.1.0",
        "fresh": "^2.0.0",
        "http-errors": "^2.0.0",
        "merge-descriptors": "^2.0.0",
        "mime-types": "^3.0.0",
        "on-finished": "^2.4.1",
        "once": "^1.4.0",
        "parseurl": "^1.3.3",
        "proxy-addr": "^2.0.7",
        "qs": "^6.14.0",
        "range-parser": "^1.2.1",
        "router": "^2.2.0",
        "send": "^1.1.0",
        "serve-static": "^2.2.0",
        "statuses": "^2.0.1",
        "type-
Download .txt
gitextract_00f0ctj7/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── dotnet.yml
│       ├── driver-function.yml
│       ├── finder-kotlin.yml
│       ├── finder-node.js.yml
│       ├── finder-python.yml
│       ├── finder-ruby.yml
│       ├── nodejs.yml
│       └── publish.yml
├── .gitignore
├── .vscode/
│   └── launch.json
├── LICENSE
├── README.md
├── driver/
│   ├── .eslintrc.json
│   ├── .gitignore
│   ├── .npmrc
│   ├── .releaserc
│   ├── CHANGELOG.md
│   ├── eslint.config.mjs
│   ├── lib/
│   │   ├── commands/
│   │   │   ├── assertions.ts
│   │   │   ├── clipboard.ts
│   │   │   ├── context.ts
│   │   │   ├── element.ts
│   │   │   ├── execute/
│   │   │   │   ├── scroll.ts
│   │   │   │   └── wait.ts
│   │   │   ├── execute.ts
│   │   │   ├── gesture.ts
│   │   │   └── screen.ts
│   │   ├── desired-caps.ts
│   │   ├── doctor/
│   │   │   └── checks.js
│   │   ├── driver.ts
│   │   ├── ios/
│   │   │   └── app.ts
│   │   ├── logger.ts
│   │   ├── platform.ts
│   │   └── sessions/
│   │       ├── android.ts
│   │       ├── base64url.ts
│   │       ├── ios.ts
│   │       ├── isolate_socket.ts
│   │       ├── log-monitor.ts
│   │       ├── observatory.ts
│   │       └── session.ts
│   ├── npm-shrinkwrap.json
│   ├── package.json
│   ├── release.sh
│   ├── scripts/
│   │   └── download-wda-sim.mjs
│   └── tsconfig.json
├── example/
│   ├── .gitignore
│   ├── dart/
│   │   ├── drag_commands.dart
│   │   └── get_text_command.dart
│   ├── flutter_app_under_test/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── android/
│   │   │   ├── app/
│   │   │   │   ├── build.gradle
│   │   │   │   └── src/
│   │   │   │       ├── debug/
│   │   │   │       │   └── AndroidManifest.xml
│   │   │   │       ├── main/
│   │   │   │       │   ├── AndroidManifest.xml
│   │   │   │       │   ├── java/
│   │   │   │       │   │   └── com/
│   │   │   │       │   │       └── example/
│   │   │   │       │   │           └── hello_world/
│   │   │   │       │   │               └── MainActivity.java
│   │   │   │       │   └── res/
│   │   │   │       │       ├── drawable/
│   │   │   │       │       │   └── launch_background.xml
│   │   │   │       │       └── values/
│   │   │   │       │           └── styles.xml
│   │   │   │       └── profile/
│   │   │   │           └── AndroidManifest.xml
│   │   │   ├── build.gradle
│   │   │   ├── gradle/
│   │   │   │   └── wrapper/
│   │   │   │       └── gradle-wrapper.properties
│   │   │   ├── gradle.properties
│   │   │   └── settings.gradle
│   │   ├── ios/
│   │   │   ├── Flutter/
│   │   │   │   ├── AppFrameworkInfo.plist
│   │   │   │   ├── Debug.xcconfig
│   │   │   │   └── Release.xcconfig
│   │   │   ├── Runner/
│   │   │   │   ├── AppDelegate.h
│   │   │   │   ├── AppDelegate.m
│   │   │   │   ├── Assets.xcassets/
│   │   │   │   │   ├── AppIcon.appiconset/
│   │   │   │   │   │   └── Contents.json
│   │   │   │   │   └── LaunchImage.imageset/
│   │   │   │   │       ├── Contents.json
│   │   │   │   │       └── README.md
│   │   │   │   ├── Base.lproj/
│   │   │   │   │   ├── LaunchScreen.storyboard
│   │   │   │   │   └── Main.storyboard
│   │   │   │   ├── Info.plist
│   │   │   │   └── main.m
│   │   │   ├── Runner.xcodeproj/
│   │   │   │   ├── project.pbxproj
│   │   │   │   ├── project.xcworkspace/
│   │   │   │   │   └── contents.xcworkspacedata
│   │   │   │   └── xcshareddata/
│   │   │   │       └── xcschemes/
│   │   │   │           └── Runner.xcscheme
│   │   │   └── Runner.xcworkspace/
│   │   │       └── contents.xcworkspacedata
│   │   ├── lib/
│   │   │   ├── main.dart
│   │   │   ├── main.g.dart
│   │   │   └── stream.dart
│   │   ├── pubspec.yaml
│   │   ├── test/
│   │   │   └── widget_test.dart
│   │   └── test_driver/
│   │       └── main_test.dart
│   ├── java/
│   │   ├── .gitignore
│   │   ├── .vscode/
│   │   │   └── settings.json
│   │   ├── pom.xml
│   │   └── src/
│   │       └── test/
│   │           └── java/
│   │               └── example/
│   │                   └── appium/
│   │                       ├── BaseDriver.java
│   │                       └── FlutterTest.java
│   ├── nodejs/
│   │   ├── README.md
│   │   ├── package.json
│   │   └── src/
│   │       └── index.js
│   ├── python/
│   │   ├── example.py
│   │   └── requirement.txt
│   ├── ruby/
│   │   ├── Gemfile
│   │   ├── example.rb
│   │   ├── example_sample2.rb
│   │   └── example_sample2_ios.rb
│   └── sample2/
│       ├── README.md
│       └── app-debug.apk
├── finder/
│   ├── dotnet/
│   │   ├── AppiumFlutterFinder/
│   │   │   ├── AppiumFlutterFinder.csproj
│   │   │   ├── FlutterBy.cs
│   │   │   ├── FlutterDriver.cs
│   │   │   ├── FlutterElement.cs
│   │   │   ├── FlutterElementFactory.cs
│   │   │   ├── License.md
│   │   │   └── Readme.md
│   │   ├── AppiumFlutterFinderTests/
│   │   │   ├── AppiumFlutterFinderTests.csproj
│   │   │   ├── FlutterFinderTests.cs
│   │   │   └── Usings.cs
│   │   └── DotNetAppiumFlutterFinder.sln
│   ├── kotlin/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   ├── gradle/
│   │   │   └── wrapper/
│   │   │       ├── gradle-wrapper.jar
│   │   │       └── gradle-wrapper.properties
│   │   ├── gradle.properties
│   │   ├── gradlew
│   │   ├── gradlew.bat
│   │   ├── settings.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   └── kotlin/
│   │       │       └── pro/
│   │       │           └── truongsinh/
│   │       │               └── appium_flutter/
│   │       │                   ├── FlutterFinder.kt
│   │       │                   └── finder/
│   │       │                       ├── FlutterElement.kt
│   │       │                       ├── ancestor.kt
│   │       │                       ├── bySemanticsLabel.kt
│   │       │                       ├── byTooltip.kt
│   │       │                       ├── byType.kt
│   │       │                       ├── byValueKey.kt
│   │       │                       ├── descendant.kt
│   │       │                       ├── pageback.kt
│   │       │                       ├── serializer.kt
│   │       │                       └── text.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── pro/
│   │                   └── truongsinh/
│   │                       └── appium_flutter/
│   │                           └── finder/
│   │                               └── FinderTest.kt
│   ├── nodejs/
│   │   ├── LICENSE
│   │   ├── README.md
│   │   ├── lib/
│   │   │   ├── base64url.ts
│   │   │   ├── base64url_test.ts
│   │   │   ├── deserializer.ts
│   │   │   ├── serializer.ts
│   │   │   └── serializer_test.ts
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── tslint.json
│   ├── python/
│   │   ├── .pylintrc
│   │   ├── README.md
│   │   ├── appium_flutter_finder/
│   │   │   ├── __init__.py
│   │   │   └── flutter_finder.py
│   │   ├── setup.py
│   │   └── tests/
│   │       ├── __init__.py
│   │       └── flutter_finder_tests.py
│   ├── ruby/
│   │   ├── Gemfile
│   │   ├── README.md
│   │   ├── Rakefile
│   │   ├── appium_flutter_finder.gemspec
│   │   ├── lib/
│   │   │   ├── appium_flutter_finder/
│   │   │   │   └── version.rb
│   │   │   └── appium_flutter_finder.rb
│   │   └── test/
│   │       ├── appium_flutter_finder_test.rb
│   │       └── test_helper.rb
│   └── spec.json
└── jitpack.yml
Download .txt
SYMBOL INDEX (228 symbols across 36 files)

FILE: driver/lib/commands/assertions.ts
  type FinderInput (line 5) | type FinderInput =
  function getFinderBase64 (line 25) | function getFinderBase64(input: FinderInput): string {
  function executeAssertion (line 56) | async function executeAssertion(

FILE: driver/lib/commands/context.ts
  constant FLUTTER_CONTEXT_NAME (line 4) | const FLUTTER_CONTEXT_NAME = `FLUTTER`;
  constant NATIVE_CONTEXT_NAME (line 5) | const NATIVE_CONTEXT_NAME = `NATIVE_APP`;

FILE: driver/lib/commands/execute.ts
  type CommandHandler (line 20) | type CommandHandler = (driver: FlutterDriver, ...args: any[]) => Promise...
  type CommandMap (line 21) | type CommandMap = Record<string, CommandHandler>;
  type DragAndDropParams (line 23) | interface DragAndDropParams {
  type DiagnosticsOptions (line 31) | interface DiagnosticsOptions {
  type LongTapOptions (line 36) | interface LongTapOptions {
  type OffsetOptions (line 41) | interface OffsetOptions {

FILE: driver/lib/driver.ts
  type FluttertDriverConstraints (line 40) | type FluttertDriverConstraints = typeof desiredCapConstraints;
  constant WEBVIEW_NO_PROXY (line 42) | const WEBVIEW_NO_PROXY = [
  class FlutterDriver (line 56) | class FlutterDriver extends BaseDriver<FluttertDriverConstraints> {
    method constructor (line 106) | constructor(opts, shouldValidateCaps: boolean) {
    method createSession (line 120) | public async createSession(
    method deleteSession (line 135) | public async deleteSession() {
    method installApp (line 172) | public async installApp(appPath: string, opts = {}) {
    method activateApp (line 177) | public async activateApp(appId: string) {
    method terminateApp (line 183) | public async terminateApp(appId: string) {
    method back (line 188) | public async back() {
    method getOrientation (line 193) | public async getOrientation(): Promise<string | null> {
    method setOrientation (line 205) | public async setOrientation(orientation: string) {
    method validateLocatorStrategy (line 218) | public validateLocatorStrategy(strategy: string) {
    method validateDesiredCaps (line 226) | validateDesiredCaps(
    method proxyCommand (line 239) | public async proxyCommand(url: string, method: string, body = null) {
    method executeCommand (line 245) | public async executeCommand(
    method getProxyAvoidList (line 289) | public getProxyAvoidList(): RouteMatcher[] {
    method proxyActive (line 297) | public proxyActive(): boolean {
    method canProxy (line 306) | public canProxy(): boolean {

FILE: driver/lib/platform.ts
  constant PLATFORM (line 1) | const PLATFORM = {

FILE: driver/lib/sessions/android.ts
  function startAndroidSession (line 9) | async function startAndroidSession(
  function connectAndroidSession (line 39) | async function connectAndroidSession(
  function getObservatoryWsUri (line 49) | async function getObservatoryWsUri(

FILE: driver/lib/sessions/ios.ts
  constant LOCALHOST (line 13) | const LOCALHOST = `127.0.0.1`;
  function startIOSSession (line 15) | async function startIOSSession(
  function connectIOSSession (line 45) | async function connectIOSSession(
  function requireFreePort (line 55) | async function requireFreePort(this: FlutterDriver, port: number) {
  function getObservatoryWsUri (line 80) | async function getObservatoryWsUri(

FILE: driver/lib/sessions/isolate_socket.ts
  type ExecuteArgs (line 3) | interface ExecuteArgs {
  class IsolateSocket (line 8) | class IsolateSocket extends Client {
    method executeSocketCommand (line 10) | public async executeSocketCommand(args: ExecuteArgs) {

FILE: driver/lib/sessions/log-monitor.ts
  type LogEntry (line 3) | interface LogEntry {
  constant DEFAULT_MAX_RETRY_COUNT (line 9) | const DEFAULT_MAX_RETRY_COUNT = 10;
  constant DEFAULT_BACKOFF_TIME_MS (line 10) | const DEFAULT_BACKOFF_TIME_MS = 3000;
  type Filter (line 12) | type Filter = (x: LogEntry) => Promise<boolean>;
  class LogMonitor (line 14) | class LogMonitor {
    method constructor (line 20) | constructor(logsEmitter: EventEmitter, filter: Filter) {
    method started (line 27) | get started(): boolean {
    method clearlastMatch (line 31) | clearlastMatch() {
    method lastMatch (line 35) | get lastMatch(): LogEntry | null {
    method waitForLastMatchExist (line 39) | async waitForLastMatchExist(
    method start (line 55) | start(): this {
    method stop (line 66) | stop(): this {
    method _onOutput (line 77) | private async _onOutput(logEntry: LogEntry): Promise<void> {

FILE: driver/lib/sessions/observatory.ts
  constant OBSERVATORY_URL_PATTERN (line 14) | const OBSERVATORY_URL_PATTERN = new RegExp(
  function connectSocket (line 25) | async function connectSocket(
  function executeGetIsolateCommand (line 141) | async function executeGetIsolateCommand(this: FlutterDriver, isolateId: ...
  function executeGetVMCommand (line 150) | async function executeGetVMCommand(this: FlutterDriver) {
  function executeElementCommand (line 164) | async function executeElementCommand(
  function extractObservatoryUrl (line 183) | function extractObservatoryUrl(logEntry: LogEntry): URL | null {

FILE: driver/scripts/download-wda-sim.mjs
  function parseArgValue (line 23) | function parseArgValue(argName) {
  function webdriveragentPkgVersion (line 38) | async function webdriveragentPkgVersion() {
  function prepareRootDir (line 56) | async function prepareRootDir() {
  function getWDAPrebuiltPackage (line 69) | async function getWDAPrebuiltPackage() {

FILE: example/dart/drag_commands.dart
  class DragCommand (line 9) | class DragCommand extends Command {
  class DragResult (line 30) | class DragResult extends Result {
    method toJson (line 36) | Map<String, dynamic> toJson()
  class DragCommandExtension (line 44) | class DragCommandExtension extends CommandExtension {
    method call (line 46) | Future<Result> call(Command command, WidgetController prober,
    method deserialize (line 62) | Command deserialize(

FILE: example/dart/get_text_command.dart
  class Base64URL (line 10) | class Base64URL {
    method encode (line 11) | String encode(String str)
    method decode (line 16) | String decode(String str)
  class FinderHelper (line 33) | class FinderHelper {
    method deserializeBase64 (line 34) | SerializableFinder deserializeBase64(String base64Str)
  class GetTextCommandExtension (line 102) | class GetTextCommandExtension extends CommandExtension {
    method getTextFromWidget (line 103) | String? getTextFromWidget(Text widget)
    method call (line 108) | Future<Result> call(
    method deserialize (line 147) | Command deserialize(
  class GetTextCommand (line 155) | class GetTextCommand extends Command {
  class GetTextResult (line 167) | class GetTextResult extends Result {
    method toJson (line 174) | Map<String, dynamic> toJson()

FILE: example/flutter_app_under_test/android/app/src/main/java/com/example/hello_world/MainActivity.java
  class MainActivity (line 7) | public class MainActivity extends FlutterActivity {
    method onCreate (line 8) | @Override

FILE: example/flutter_app_under_test/lib/main.dart
  function main (line 8) | void main()
  function myApp (line 15) | Widget myApp()
  function myHomePage (line 23) | Widget myHomePage(BuildContext context, {String title})
  function secondPage (line 82) | Widget secondPage()

FILE: example/flutter_app_under_test/lib/main.g.dart
  class MyApp (line 9) | class MyApp extends StatelessWidget {
    method build (line 13) | Widget build(BuildContext _context)
  class MyHomePage (line 16) | class MyHomePage extends StatelessWidget {
    method build (line 22) | Widget build(BuildContext _context)
  class SecondPage (line 25) | class SecondPage extends StatelessWidget {
    method build (line 29) | Widget build(BuildContext _context)

FILE: example/flutter_app_under_test/lib/stream.dart
  function init (line 12) | void init()

FILE: example/flutter_app_under_test/test/widget_test.dart
  function main (line 13) | void main()

FILE: example/flutter_app_under_test/test_driver/main_test.dart
  function main (line 5) | void main()

FILE: example/java/src/test/java/example/appium/BaseDriver.java
  class BaseDriver (line 15) | public class BaseDriver {
    method setUp (line 20) | @Before
    method tearDown (line 48) | @After
    method waitForFirstFrame (line 58) | public void waitForFirstFrame(){

FILE: example/java/src/test/java/example/appium/FlutterTest.java
  class FlutterTest (line 18) | public class FlutterTest extends BaseDriver {
    method setUp (line 20) | @Before
    method basicTest (line 26) | @Test
    method clickToElement (line 121) | private void clickToElement(String locator){
    method waitFor (line 126) | private MobileElement waitFor(String locator){
    method validateElementPosition (line 130) | private void validateElementPosition(MobileElement buttonFinder) {

FILE: example/ruby/example.rb
  class ExampleTests (line 6) | class ExampleTests < Minitest::Test
    method test_run_example_ios_scenario (line 24) | def test_run_example_ios_scenario

FILE: example/ruby/example_sample2.rb
  class ExampleTests (line 6) | class ExampleTests < Minitest::Test
    method setup (line 26) | def setup
    method teardown (line 31) | def teardown
    method test_run_example_android (line 35) | def test_run_example_android

FILE: example/ruby/example_sample2_ios.rb
  class ExampleTests (line 6) | class ExampleTests < Minitest::Test
    method setup (line 28) | def setup
    method teardown (line 33) | def teardown
    method test_run_example_ios (line 37) | def test_run_example_ios

FILE: finder/dotnet/AppiumFlutterFinder/FlutterBy.cs
  class FlutterBy (line 7) | public class FlutterBy : By
    method FlutterBy (line 14) | private FlutterBy(dynamic searchCriteria)
    method ByKeyValue (line 19) | public static FlutterBy ByKeyValue(string key) => new FlutterBy(new
    method ByKeyValue (line 26) | public static FlutterBy ByKeyValue(int key) => new FlutterBy(new
    method ByText (line 33) | public static FlutterBy ByText(string text) => new FlutterBy(new
    method ByTooltip (line 38) | public static FlutterBy ByTooltip(string text) => new FlutterBy(new
    method ByType (line 43) | public static FlutterBy ByType(string type) => new FlutterBy(new
    method BySemanticsLabel (line 49) | public static FlutterBy BySemanticsLabel(string label) => new FlutterB...
    method BySemanticsLabel (line 55) | public static FlutterBy BySemanticsLabel(Regex pattern) => new Flutter...
    method PageBack (line 62) | public static FlutterBy PageBack() => new FlutterBy(new
    method ByAnscestor (line 67) | public static FlutterBy ByAnscestor(FlutterBy of,
    method ByDescendant (line 80) | public static FlutterBy ByDescendant(FlutterBy of,

FILE: finder/dotnet/AppiumFlutterFinder/FlutterDriver.cs
  class FlutterDriver (line 17) | public class FlutterDriver : AppiumDriver<FlutterElement>
    method FlutterDriver (line 20) | public FlutterDriver(ICapabilities appiumOptions) : base(appiumOptions)
    method FlutterDriver (line 24) | public FlutterDriver(ICommandExecutor commandExecutor, ICapabilities a...
    method FlutterDriver (line 28) | public FlutterDriver(ICapabilities appiumOptions, TimeSpan commandTime...
    method FlutterDriver (line 34) | public FlutterDriver(AppiumServiceBuilder builder, ICapabilities appiu...
    method FlutterDriver (line 38) | public FlutterDriver(Uri remoteAddress, ICapabilities appiumOptions) :...
    method FlutterDriver (line 42) | public FlutterDriver(AppiumLocalService service, ICapabilities appiumO...
    method FlutterDriver (line 47) | public FlutterDriver(AppiumServiceBuilder builder, ICapabilities appiu...
    method FlutterDriver (line 51) | public FlutterDriver(Uri remoteAddress, ICapabilities appiumOptions, T...
    method CreateElementFactory (line 55) | protected override RemoteWebElementFactory CreateElementFactory() => n...
    method FlutterDriver (line 56) | public FlutterDriver(AppiumLocalService service, ICapabilities appiumO...
    method WaitForFirstFrame (line 63) | public void WaitForFirstFrame()
    method SwitchToAndroidDriver (line 68) | public void SwitchToAndroidDriver()
    method SwitchToFlutterDriver (line 71) | public void SwitchToFlutterDriver()
    method WaitForElementToBeVisible (line 74) | public FlutterElement WaitForElementToBeVisible(FlutterBy by, int wait...
    method WaitForElementToBeClickable (line 81) | public FlutterElement WaitForElementToBeClickable(FlutterBy by, int wa...
    method SetFrameSync (line 87) | public void SetFrameSync(bool isSet, int wait = 60)
    method WaitForAbscense (line 91) | public void WaitForAbscense(FlutterBy by, int wait = 60)
    method SaveScreenshot (line 94) | public void SaveScreenshot(string name)
    method RunUnsynchronized (line 100) | public void RunUnsynchronized(Action action)
    method Click (line 106) | public void Click(FlutterBy by)
    method Tap (line 109) | public void Tap(double x, double y, long? count = null)
    method PageBack (line 118) | public void PageBack()

FILE: finder/dotnet/AppiumFlutterFinder/FlutterElement.cs
  class FlutterElement (line 11) | public class FlutterElement : AppiumWebElement
    method FlutterElement (line 13) | public FlutterElement(RemoteWebDriver parent, FlutterBy by) : base(par...
    method FlutterElement (line 17) | public FlutterElement(RemoteWebDriver parent, string id) : base(parent...
    method FocusAndSendKeys (line 21) | public void FocusAndSendKeys(string text)

FILE: finder/dotnet/AppiumFlutterFinder/FlutterElementFactory.cs
  class FlutterElementFactory (line 12) | public class FlutterElementFactory : CachedElementFactory<FlutterElement>
    method FlutterElementFactory (line 14) | public FlutterElementFactory(RemoteWebDriver parentDriver): base(paren...
    method CreateCachedElement (line 18) | protected override FlutterElement CreateCachedElement(RemoteWebDriver ...

FILE: finder/dotnet/AppiumFlutterFinderTests/FlutterFinderTests.cs
  class Tests (line 6) | public class Tests
    method TestByAncestor (line 9) | [Test]
    method TestBydescendant (line 22) | [Test]
    method test_by_semantics_label (line 34) | [Test]
    method TestBySemanticsLabelRegex (line 41) | [Test]
    method TestByTooltip (line 47) | [Test]
    method TestByType (line 54) | [Test]
    method TestByValueKeyInt (line 61) | [Test]
    method TestByValueKeyString (line 68) | [Test]
    method TestPageBack (line 75) | [Test]
    method TestByText (line 82) | [Test]

FILE: finder/nodejs/lib/base64url.ts
  constant W3C_ELEMENT (line 1) | const W3C_ELEMENT: string = `element-6066-11e4-a52e-4f735466cecf`;
  constant MJSON_ELEMENT (line 2) | const MJSON_ELEMENT: string = `ELEMENT`;

FILE: finder/nodejs/lib/serializer.ts
  function serialize (line 5) | function serialize(obj: object) {
  type SerializableFinder (line 9) | type SerializableFinder = string;
  type Pattern (line 10) | type Pattern = string | RegExp;

FILE: finder/python/appium_flutter_finder/flutter_finder.py
  class FlutterElement (line 7) | class FlutterElement(WebElement):
  class FlutterFinder (line 10) | class FlutterFinder:
    method by_ancestor (line 11) | def by_ancestor(self, serialized_finder, matching, match_root=False, f...
    method by_descendant (line 20) | def by_descendant(self, serialized_finder, matching, match_root=False,...
    method by_semantics_label (line 29) | def by_semantics_label(self, label, isRegExp=False):
    method by_tooltip (line 36) | def by_tooltip(self, text):
    method by_text (line 42) | def by_text(self, text):
    method by_type (line 48) | def by_type(self, type_):
    method by_value_key (line 54) | def by_value_key(self, key):
    method page_back (line 61) | def page_back(self):
    method _serialize (line 66) | def _serialize(self, finder_dict):
    method _by_ancestor_or_descendant (line 71) | def _by_ancestor_or_descendant(self, type_, serialized_finder, matchin...

FILE: finder/python/tests/flutter_finder_tests.py
  class FlutterFinderTest (line 6) | class FlutterFinderTest(unittest.TestCase):
    method test_by_ancestor (line 7) | def test_by_ancestor(self):
    method test_by_descendant (line 22) | def test_by_descendant(self):
    method test_by_semantics_label (line 36) | def test_by_semantics_label(self):
    method test_by_semantics_label_regex (line 40) | def test_by_semantics_label_regex(self):
    method test_by_tooltip (line 44) | def test_by_tooltip(self):
    method test_by_type (line 48) | def test_by_type(self):
    method test_by_key_value_int (line 52) | def test_by_key_value_int(self):
    method test_by_key_value_string (line 56) | def test_by_key_value_string(self):
    method test_page_back (line 60) | def test_page_back(self):
    method test_by_text (line 64) | def test_by_text(self):

FILE: finder/ruby/lib/appium_flutter_finder.rb
  type Appium (line 7) | module Appium
    type Flutter (line 8) | module Flutter
      class Element (line 11) | class Element < ::Selenium::WebDriver::Element
        method initialize (line 14) | def initialize(driver, finder:)
      type Finder (line 21) | module Finder
        function by_ancestor (line 22) | def by_ancestor(serialized_finder:, matching:, match_root: false, ...
        function by_descendant (line 32) | def by_descendant(serialized_finder:, matching:, match_root: false...
        function by_semantics_label (line 42) | def by_semantics_label(label)
        function by_tooltip (line 51) | def by_tooltip(text)
        function by_type (line 58) | def by_type(type)
        function by_value_key (line 65) | def by_value_key(key)
        function page_back (line 73) | def page_back
        function by_text (line 79) | def by_text(text)
        function serialize (line 88) | def serialize(hash)
        function by_ancestor_or_descendant (line 92) | def by_ancestor_or_descendant(type:, serialized_finder:, matching:...

FILE: finder/ruby/lib/appium_flutter_finder/version.rb
  type Appium (line 1) | module Appium
    type Flutter (line 2) | module Flutter
      type Finder (line 3) | module Finder

FILE: finder/ruby/test/appium_flutter_finder_test.rb
  class AppiumFlutterFinderTest (line 4) | class AppiumFlutterFinderTest < Minitest::Test
    method test_by_ancestor (line 7) | def test_by_ancestor
    method test_by_descendant (line 24) | def test_by_descendant
    method test_by_semantics_label (line 40) | def test_by_semantics_label
    method test_by_semantics_label_regex (line 47) | def test_by_semantics_label_regex
    method test_by_tooltip (line 54) | def test_by_tooltip
    method test_by_type (line 61) | def test_by_type
    method test_by_key_value_int (line 68) | def test_by_key_value_int
    method test_by_key_value_string (line 75) | def test_by_key_value_string
    method test_page_back (line 82) | def test_page_back
    method test_by_text (line 86) | def test_by_text
Condensed preview — 159 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (828K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 1068,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/workflows/dotnet.yml",
    "chars": 837,
    "preview": "# This workflow will build a .NET project\n# For more information see: https://docs.github.com/en/actions/automating-buil"
  },
  {
    "path": ".github/workflows/driver-function.yml",
    "chars": 4161,
    "preview": "name: Run quick functionality check\n\non:\n  workflow_dispatch:\n  push:\n    paths:\n      - 'driver/**'\n    branches: [ mai"
  },
  {
    "path": ".github/workflows/finder-kotlin.yml",
    "chars": 522,
    "preview": "name: Kotlin\n\non:\n  workflow_dispatch:\n  push:\n    paths:\n      - 'finder/kotlin/**'\n    branches: [ main ]\n  pull_reque"
  },
  {
    "path": ".github/workflows/finder-node.js.yml",
    "chars": 1429,
    "preview": "# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests ac"
  },
  {
    "path": ".github/workflows/finder-python.yml",
    "chars": 885,
    "preview": "name: Python\n\non:\n  workflow_dispatch:\n  push:\n    paths:\n      - 'finder/python/**'\n    branches: [ main ]\n  pull_reque"
  },
  {
    "path": ".github/workflows/finder-ruby.yml",
    "chars": 1003,
    "preview": "# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n"
  },
  {
    "path": ".github/workflows/nodejs.yml",
    "chars": 1138,
    "preview": "# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests ac"
  },
  {
    "path": ".github/workflows/publish.yml",
    "chars": 1282,
    "preview": "# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created\n# For "
  },
  {
    "path": ".gitignore",
    "chars": 773,
    "preview": ".DS_Store\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\npackage-lock.json*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instru"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 789,
    "preview": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  //"
  },
  {
    "path": "LICENSE",
    "chars": 1109,
    "preview": "MIT License\n-----------\n\nCopyright (c) 2019 TruongSinh Tran-Nguyen <i@truongsinh.pro>\nPermission is hereby granted, free"
  },
  {
    "path": "README.md",
    "chars": 37453,
    "preview": "# Appium Flutter Driver\n\n[![NPM version](https://img.shields.io/npm/v/appium-flutter-driver.svg)](https://npmjs.org/pack"
  },
  {
    "path": "driver/.eslintrc.json",
    "chars": 53,
    "preview": "{\n  \"extends\": [\"@appium/eslint-config-appium-ts\"]\n}\n"
  },
  {
    "path": "driver/.gitignore",
    "chars": 41,
    "preview": "# copy from parent dir\nLICENSE\nREADME.md\n"
  },
  {
    "path": "driver/.npmrc",
    "chars": 18,
    "preview": "package-lock=false"
  },
  {
    "path": "driver/.releaserc",
    "chars": 1406,
    "preview": "\n{\n  \"plugins\": [\n    [\"@semantic-release/commit-analyzer\", {\n      \"preset\": \"angular\",\n      \"releaseRules\": [\n       "
  },
  {
    "path": "driver/CHANGELOG.md",
    "chars": 9762,
    "preview": "# Changelog\n\n## 3.6.0\n- Update Appium XCUITest driver dependency to 10.43.1\n- Update Appium UIAutomator2 driver dependen"
  },
  {
    "path": "driver/eslint.config.mjs",
    "chars": 99,
    "preview": "import appiumConfig from '@appium/eslint-config-appium-ts';\n\nexport default [\n  ...appiumConfig\n];\n"
  },
  {
    "path": "driver/lib/commands/assertions.ts",
    "chars": 2581,
    "preview": "import {FlutterDriver} from '../driver';\nimport {byValueKey, byText, byTooltip} from 'appium-flutter-finder';\nimport typ"
  },
  {
    "path": "driver/lib/commands/clipboard.ts",
    "chars": 862,
    "preview": "import type {FlutterDriver} from '../driver';\n\n/**\n * Set clipboard content via each native app driver\n * @param this th"
  },
  {
    "path": "driver/lib/commands/context.ts",
    "chars": 2088,
    "preview": "import type {FlutterDriver} from '../driver';\nimport {log} from '../logger';\n\nexport const FLUTTER_CONTEXT_NAME = `FLUTT"
  },
  {
    "path": "driver/lib/commands/element.ts",
    "chars": 881,
    "preview": "import {FlutterDriver} from '../driver';\n\nexport const getText = async function (this: FlutterDriver, el: string): Promi"
  },
  {
    "path": "driver/lib/commands/execute/scroll.ts",
    "chars": 5648,
    "preview": "import _ from 'lodash';\nimport {FlutterDriver} from '../../driver';\nimport {waitFor, waitForTappable} from './wait';\n\nex"
  },
  {
    "path": "driver/lib/commands/execute/wait.ts",
    "chars": 884,
    "preview": "import {FlutterDriver} from '../../driver';\n\nconst waitForConstructor =\n  (command: `waitForAbsent` | `waitFor` | `waitF"
  },
  {
    "path": "driver/lib/commands/execute.ts",
    "chars": 7207,
    "preview": "/* eslint-disable @typescript-eslint/no-non-null-assertion */\nimport type {FlutterDriver} from '../driver';\nimport {reCo"
  },
  {
    "path": "driver/lib/commands/gesture.ts",
    "chars": 2172,
    "preview": "import type {FlutterDriver} from '../driver';\nimport {longTap as longClick} from './execute/scroll';\n\nexport const click"
  },
  {
    "path": "driver/lib/commands/screen.ts",
    "chars": 300,
    "preview": "import type {FlutterDriver} from '../driver';\nimport {IsolateSocket} from '../sessions/isolate_socket';\n\nexport const ge"
  },
  {
    "path": "driver/lib/desired-caps.ts",
    "chars": 579,
    "preview": "export const desiredCapConstraints = {\n  app: {\n    isString: true,\n  },\n  avd: {\n    isString: true,\n  },\n  maxRetryCou"
  },
  {
    "path": "driver/lib/doctor/checks.js",
    "chars": 1457,
    "preview": "import {doctor as androidDoctor} from 'appium-android-driver';\nimport {doctor as iosDoctor} from 'appium-xcuitest-driver"
  },
  {
    "path": "driver/lib/driver.ts",
    "chars": 10964,
    "preview": "// @ts-ignore: no 'errors' export module\nimport _ from 'lodash';\nimport {BaseDriver} from 'appium/driver';\nimport {log a"
  },
  {
    "path": "driver/lib/ios/app.ts",
    "chars": 1041,
    "preview": "import {services, INSTRUMENT_CHANNEL} from 'appium-ios-device';\nimport {log} from './../logger';\n\n/**\n * Launch the give"
  },
  {
    "path": "driver/lib/logger.ts",
    "chars": 153,
    "preview": "import {logger} from '@appium/support';\nimport {AppiumLogger} from '@appium/types';\n\nexport const log: AppiumLogger = lo"
  },
  {
    "path": "driver/lib/platform.ts",
    "chars": 74,
    "preview": "export const PLATFORM = {\n  IOS: 'ios',\n  ANDROID: 'android',\n} as const;\n"
  },
  {
    "path": "driver/lib/sessions/android.ts",
    "chars": 3644,
    "preview": "import {AndroidUiautomator2Driver} from 'appium-uiautomator2-driver';\nimport {connectSocket, extractObservatoryUrl, OBSE"
  },
  {
    "path": "driver/lib/sessions/base64url.ts",
    "chars": 772,
    "preview": "import _ from 'lodash';\nimport {util} from '@appium/support';\n\nexport const decode = (\n  input: string | {ELEMENT: strin"
  },
  {
    "path": "driver/lib/sessions/ios.ts",
    "chars": 6094,
    "preview": "import {utilities} from 'appium-ios-device';\nimport {XCUITestDriver} from 'appium-xcuitest-driver';\nimport B from 'blueb"
  },
  {
    "path": "driver/lib/sessions/isolate_socket.ts",
    "chars": 464,
    "preview": "import {Client} from 'rpc-websockets';\n\ninterface ExecuteArgs {\n  command: string;\n  [key: string]: any;\n}\n\nexport class"
  },
  {
    "path": "driver/lib/sessions/log-monitor.ts",
    "chars": 2163,
    "preview": "import type {EventEmitter} from 'node:events';\nimport {retryInterval} from 'asyncbox';\nexport interface LogEntry {\n  tim"
  },
  {
    "path": "driver/lib/sessions/observatory.ts",
    "chars": 6955,
    "preview": "import {URL} from 'node:url';\nimport _ from 'lodash';\nimport type {FlutterDriver} from '../driver';\nimport {IsolateSocke"
  },
  {
    "path": "driver/lib/sessions/session.ts",
    "chars": 2634,
    "preview": "import type {FlutterDriver} from '../driver';\nimport _ from 'lodash';\nimport {startAndroidSession, connectAndroidSession"
  },
  {
    "path": "driver/npm-shrinkwrap.json",
    "chars": 456138,
    "preview": "{\n  \"name\": \"appium-flutter-driver\",\n  \"version\": \"3.6.0\",\n  \"lockfileVersion\": 3,\n  \"requires\": true,\n  \"packages\": {\n "
  },
  {
    "path": "driver/package.json",
    "chars": 2351,
    "preview": "{\n  \"name\": \"appium-flutter-driver\",\n  \"description\": \"Appium Flutter driver\",\n  \"keywords\": [\n    \"appium\",\n    \"flutte"
  },
  {
    "path": "driver/release.sh",
    "chars": 349,
    "preview": "# !/bin/sh\n\necho \"refreshing dependencies\"\nrm npm-shrinkwrap.json\nAPPIUM_SKIP_CHROMEDRIVER_INSTALL=1 npm run clean-depen"
  },
  {
    "path": "driver/scripts/download-wda-sim.mjs",
    "chars": 2934,
    "preview": "// TODO: Please revert '@appium/support' to 'appium/support.js' and \"@appium/support\" dependency\n// in Appium 3 based ve"
  },
  {
    "path": "driver/tsconfig.json",
    "chars": 300,
    "preview": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@appium/tsconfig/tsconfig.json\",\n  \"compilerOption"
  },
  {
    "path": "example/.gitignore",
    "chars": 6,
    "preview": "apps/\n"
  },
  {
    "path": "example/dart/drag_commands.dart",
    "chars": 1984,
    "preview": "import 'dart:async';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_driver/driver_extension.dart';\nimpo"
  },
  {
    "path": "example/dart/get_text_command.dart",
    "chars": 5347,
    "preview": "import 'dart:convert';\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:f"
  },
  {
    "path": "example/flutter_app_under_test/.gitignore",
    "chars": 1288,
    "preview": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.i"
  },
  {
    "path": "example/flutter_app_under_test/README.md",
    "chars": 543,
    "preview": "# hello_world\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\n"
  },
  {
    "path": "example/flutter_app_under_test/android/app/build.gradle",
    "chars": 1872,
    "preview": "def localProperties = new Properties()\ndef localPropertiesFile = rootProject.file('local.properties')\nif (localPropertie"
  },
  {
    "path": "example/flutter_app_under_test/android/app/src/debug/AndroidManifest.xml",
    "chars": 331,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.example.hello_world\">\n    <!-- Flu"
  },
  {
    "path": "example/flutter_app_under_test/android/app/src/main/AndroidManifest.xml",
    "chars": 1710,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.example.hello_world\">\n\n    <!-- io"
  },
  {
    "path": "example/flutter_app_under_test/android/app/src/main/java/com/example/hello_world/MainActivity.java",
    "chars": 368,
    "preview": "package com.example.hello_world;\n\nimport android.os.Bundle;\nimport io.flutter.app.FlutterActivity;\nimport io.flutter.plu"
  },
  {
    "path": "example/flutter_app_under_test/android/app/src/main/res/drawable/launch_background.xml",
    "chars": 434,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmln"
  },
  {
    "path": "example/flutter_app_under_test/android/app/src/main/res/values/styles.xml",
    "chars": 361,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"LaunchTheme\" parent=\"@android:style/Theme.Black.NoTi"
  },
  {
    "path": "example/flutter_app_under_test/android/app/src/profile/AndroidManifest.xml",
    "chars": 331,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.example.hello_world\">\n    <!-- Flu"
  },
  {
    "path": "example/flutter_app_under_test/android/build.gradle",
    "chars": 470,
    "preview": "buildscript {\n    repositories {\n        google()\n        jcenter()\n    }\n\n    dependencies {\n        classpath 'com.and"
  },
  {
    "path": "example/flutter_app_under_test/android/gradle/wrapper/gradle-wrapper.properties",
    "chars": 234,
    "preview": "#Fri Jun 23 08:50:38 CEST 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER"
  },
  {
    "path": "example/flutter_app_under_test/android/gradle.properties",
    "chars": 29,
    "preview": "org.gradle.jvmargs=-Xmx1536M\n"
  },
  {
    "path": "example/flutter_app_under_test/android/settings.gradle",
    "chars": 484,
    "preview": "include ':app'\n\ndef flutterProjectRoot = rootProject.projectDir.parentFile.toPath()\n\ndef plugins = new Properties()\ndef "
  },
  {
    "path": "example/flutter_app_under_test/ios/Flutter/AppFrameworkInfo.plist",
    "chars": 773,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "example/flutter_app_under_test/ios/Flutter/Debug.xcconfig",
    "chars": 30,
    "preview": "#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "example/flutter_app_under_test/ios/Flutter/Release.xcconfig",
    "chars": 30,
    "preview": "#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner/AppDelegate.h",
    "chars": 103,
    "preview": "#import <Flutter/Flutter.h>\n#import <UIKit/UIKit.h>\n\n@interface AppDelegate : FlutterAppDelegate\n\n@end\n"
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner/AppDelegate.m",
    "chars": 424,
    "preview": "#include \"AppDelegate.h\"\n#include \"GeneratedPluginRegistrant.h\"\n\n@implementation AppDelegate\n\n- (BOOL)application:(UIApp"
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 2519,
    "preview": "{\n  \"images\" : [\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n   "
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json",
    "chars": 391,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage.png\",\n      \"scale\" : \"1x\"\n    },\n  "
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md",
    "chars": 336,
    "preview": "# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in"
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner/Base.lproj/LaunchScreen.storyboard",
    "chars": 2377,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner/Base.lproj/Main.storyboard",
    "chars": 1605,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner/Info.plist",
    "chars": 1509,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner/main.m",
    "chars": 226,
    "preview": "#import <Flutter/Flutter.h>\n#import <UIKit/UIKit.h>\n#import \"AppDelegate.h\"\n\nint main(int argc, char* argv[]) {\n  @autor"
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner.xcodeproj/project.pbxproj",
    "chars": 20698,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 152,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:Runner.xcodepr"
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme",
    "chars": 3331,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0910\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "example/flutter_app_under_test/ios/Runner.xcworkspace/contents.xcworkspacedata",
    "chars": 152,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:Runner.xcodepr"
  },
  {
    "path": "example/flutter_app_under_test/lib/main.dart",
    "chars": 2991,
    "preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_driver/driver_extension.dart';\nimport 'package:functiona"
  },
  {
    "path": "example/flutter_app_under_test/lib/main.g.dart",
    "chars": 781,
    "preview": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'main.dart';\n\n// ****************************************************"
  },
  {
    "path": "example/flutter_app_under_test/lib/stream.dart",
    "chars": 447,
    "preview": "import 'dart:async' show StreamSink;\n\nimport 'package:rxdart/rxdart.dart'\n    show BehaviorSubject;\n\nfinal counterSubjec"
  },
  {
    "path": "example/flutter_app_under_test/pubspec.yaml",
    "chars": 386,
    "preview": "name: hello_world\ndescription: A new Flutter project.\nversion: 1.0.0+1\n\nenvironment:\n  sdk: \">=2.1.0 <3.0.0\"\n\ndependenci"
  },
  {
    "path": "example/flutter_app_under_test/test/widget_test.dart",
    "chars": 1050,
    "preview": "// This is a basic Flutter widget test.\n//\n// To perform an interaction with a widget in your test, use the WidgetTester"
  },
  {
    "path": "example/flutter_app_under_test/test_driver/main_test.dart",
    "chars": 3117,
    "preview": "// Imports the Flutter Driver API.\nimport 'package:flutter_driver/flutter_driver.dart';\nimport 'package:test/test.dart';"
  },
  {
    "path": "example/java/.gitignore",
    "chars": 41,
    "preview": "target/*\n.settings/*\n.classpath\n.project\n"
  },
  {
    "path": "example/java/.vscode/settings.json",
    "chars": 64,
    "preview": "{\n  \"java.configuration.updateBuildConfiguration\": \"automatic\"\n}"
  },
  {
    "path": "example/java/pom.xml",
    "chars": 2518,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
  },
  {
    "path": "example/java/src/test/java/example/appium/BaseDriver.java",
    "chars": 1960,
    "preview": "package example.appium;\n\nimport io.appium.java_client.AppiumDriver;\nimport io.appium.java_client.MobileElement;\nimport i"
  },
  {
    "path": "example/java/src/test/java/example/appium/FlutterTest.java",
    "chars": 5228,
    "preview": "package example.appium;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport static org.junit.Assert.assertEquals;\n\ni"
  },
  {
    "path": "example/nodejs/README.md",
    "chars": 938,
    "preview": "# To run the automation locally\n\n1. Make sure node and npm are installed locally\n2. cd to ./driver\n3. `npm install -g ty"
  },
  {
    "path": "example/nodejs/package.json",
    "chars": 471,
    "preview": "{\n  \"name\": \"appium\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"dependencies\": {\n    \"appium-f"
  },
  {
    "path": "example/nodejs/src/index.js",
    "chars": 6987,
    "preview": "// import * as wdio from 'webdriverio';\n// import * as assert from 'assert';\nconst wdio = require('webdriverio');\nconst "
  },
  {
    "path": "example/python/example.py",
    "chars": 1577,
    "preview": "import os\n\nfrom appium.webdriver import Remote\n\n# from appium.options.common.base import AppiumOptions\n# AppiumOptions a"
  },
  {
    "path": "example/python/requirement.txt",
    "chars": 60,
    "preview": "Appium-Flutter-Finder ~= 0.7.0\nAppium-Python-Client ~= 4.0.0"
  },
  {
    "path": "example/ruby/Gemfile",
    "chars": 106,
    "preview": "source 'https://rubygems.org'\n\ngem 'appium_flutter_finder'\ngem 'appium_lib_core'\ngem 'minitest', '~> 5.0'\n"
  },
  {
    "path": "example/ruby/example.rb",
    "chars": 1625,
    "preview": "require 'appium_lib_core'\nrequire 'appium_flutter_finder'\n\nrequire 'minitest/autorun'\n\nclass ExampleTests < Minitest::Te"
  },
  {
    "path": "example/ruby/example_sample2.rb",
    "chars": 2329,
    "preview": "require 'appium_lib_core'\nrequire 'appium_flutter_finder'\n\nrequire 'minitest/autorun'\n\nclass ExampleTests < Minitest::Te"
  },
  {
    "path": "example/ruby/example_sample2_ios.rb",
    "chars": 2123,
    "preview": "require 'appium_lib_core'\nrequire 'appium_flutter_finder'\n\nrequire 'minitest/autorun'\n\nclass ExampleTests < Minitest::Te"
  },
  {
    "path": "example/sample2/README.md",
    "chars": 885,
    "preview": "Sample app in https://github.com/flutter/samples/tree/master/add_to_app/fullscreen\n\n\n## Diff\n\nThe sample app has `enable"
  },
  {
    "path": "finder/dotnet/AppiumFlutterFinder/AppiumFlutterFinder.csproj",
    "chars": 1095,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net6.0</TargetFramework>\n    <ImplicitUsings>e"
  },
  {
    "path": "finder/dotnet/AppiumFlutterFinder/FlutterBy.cs",
    "chars": 3153,
    "preview": "using OpenQA.Selenium;\nusing System.Text;\nusing System.Text.RegularExpressions;\n\nnamespace AppiumFlutterFinder\n{\n    pu"
  },
  {
    "path": "finder/dotnet/AppiumFlutterFinder/FlutterDriver.cs",
    "chars": 3911,
    "preview": "using AppiumFlutterFinder;\nusing OpenQA.Selenium;\nusing OpenQA.Selenium.Appium;\nusing OpenQA.Selenium.Appium.Android;\nu"
  },
  {
    "path": "finder/dotnet/AppiumFlutterFinder/FlutterElement.cs",
    "chars": 632,
    "preview": "using OpenQA.Selenium.Appium;\nusing OpenQA.Selenium.Remote;\nusing System;\nusing System.Collections.Generic;\nusing Syste"
  },
  {
    "path": "finder/dotnet/AppiumFlutterFinder/FlutterElementFactory.cs",
    "chars": 646,
    "preview": "using OpenQA.Selenium.Appium.Android;\nusing OpenQA.Selenium.Appium;\nusing OpenQA.Selenium.Remote;\nusing System;\nusing S"
  },
  {
    "path": "finder/dotnet/AppiumFlutterFinder/License.md",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) [year] [fullname]\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "finder/dotnet/AppiumFlutterFinder/Readme.md",
    "chars": 21,
    "preview": ".NET FLUTTER FINDER."
  },
  {
    "path": "finder/dotnet/AppiumFlutterFinderTests/AppiumFlutterFinderTests.csproj",
    "chars": 766,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net6.0</TargetFramework>\n    <ImplicitUsings>e"
  },
  {
    "path": "finder/dotnet/AppiumFlutterFinderTests/FlutterFinderTests.cs",
    "chars": 4584,
    "preview": "using AppiumFlutterFinder;\nusing System.Text.RegularExpressions;\n\nnamespace AppiumFlutterFinderTests\n{\n    public class "
  },
  {
    "path": "finder/dotnet/AppiumFlutterFinderTests/Usings.cs",
    "chars": 29,
    "preview": "global using NUnit.Framework;"
  },
  {
    "path": "finder/dotnet/DotNetAppiumFlutterFinder.sln",
    "chars": 1660,
    "preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.6.3381"
  },
  {
    "path": "finder/kotlin/.gitignore",
    "chars": 18,
    "preview": ".gradle/*\nbuild/*\n"
  },
  {
    "path": "finder/kotlin/README.md",
    "chars": 1081,
    "preview": "# AppiumFlutterFinder\n\nKotlin finder elements for https://github.com/appium/appium-flutter-driver\n\n## Installation\n\nThis"
  },
  {
    "path": "finder/kotlin/build.gradle.kts",
    "chars": 765,
    "preview": "import org.gradle.jvm.tasks.Jar\n\ngroup = \"pro.truongsinh\"\nversion = \"0.0.6\"\n\nplugins {\n    id(\"kotlinx-serialization\") v"
  },
  {
    "path": "finder/kotlin/gradle/wrapper/gradle-wrapper.properties",
    "chars": 200,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
  },
  {
    "path": "finder/kotlin/gradle.properties",
    "chars": 955,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
  },
  {
    "path": "finder/kotlin/gradlew",
    "chars": 5766,
    "preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "finder/kotlin/gradlew.bat",
    "chars": 2763,
    "preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "finder/kotlin/settings.gradle.kts",
    "chars": 402,
    "preview": "rootProject.name = \"appium-flutter-finder\"\n\npluginManagement {\n    resolutionStrategy {\n        eachPlugin {\n           "
  },
  {
    "path": "finder/kotlin/src/main/kotlin/pro/truongsinh/appium_flutter/FlutterFinder.kt",
    "chars": 3101,
    "preview": "package pro.truongsinh.appium_flutter\n\nimport java.util.regex.Pattern\n\nimport org.openqa.selenium.remote.RemoteWebDriver"
  },
  {
    "path": "finder/kotlin/src/main/kotlin/pro/truongsinh/appium_flutter/finder/FlutterElement.kt",
    "chars": 387,
    "preview": "package pro.truongsinh.appium_flutter.finder\n\nimport org.openqa.selenium.remote.RemoteWebElement\n\npublic class FlutterEl"
  },
  {
    "path": "finder/kotlin/src/main/kotlin/pro/truongsinh/appium_flutter/finder/ancestor.kt",
    "chars": 502,
    "preview": "@file:JvmName(\"_FinderRawMethods\")\n@file:JvmMultifileClass\npackage pro.truongsinh.appium_flutter.finder\n\nfun ancestor(of"
  },
  {
    "path": "finder/kotlin/src/main/kotlin/pro/truongsinh/appium_flutter/finder/bySemanticsLabel.kt",
    "chars": 510,
    "preview": "@file:JvmName(\"_FinderRawMethods\")\n@file:JvmMultifileClass\npackage pro.truongsinh.appium_flutter.finder\n\nimport java.uti"
  },
  {
    "path": "finder/kotlin/src/main/kotlin/pro/truongsinh/appium_flutter/finder/byTooltip.kt",
    "chars": 252,
    "preview": "@file:JvmName(\"_FinderRawMethods\")\n@file:JvmMultifileClass\npackage pro.truongsinh.appium_flutter.finder\n\nfun byTooltip(i"
  },
  {
    "path": "finder/kotlin/src/main/kotlin/pro/truongsinh/appium_flutter/finder/byType.kt",
    "chars": 239,
    "preview": "@file:JvmName(\"_FinderRawMethods\")\n@file:JvmMultifileClass\npackage pro.truongsinh.appium_flutter.finder\n\nfun byType(inpu"
  },
  {
    "path": "finder/kotlin/src/main/kotlin/pro/truongsinh/appium_flutter/finder/byValueKey.kt",
    "chars": 468,
    "preview": "@file:JvmName(\"_FinderRawMethods\")\n@file:JvmMultifileClass\npackage pro.truongsinh.appium_flutter.finder\n\nfun byValueKey("
  },
  {
    "path": "finder/kotlin/src/main/kotlin/pro/truongsinh/appium_flutter/finder/descendant.kt",
    "chars": 506,
    "preview": "@file:JvmName(\"_FinderRawMethods\")\n@file:JvmMultifileClass\npackage pro.truongsinh.appium_flutter.finder\n\nfun descendant("
  },
  {
    "path": "finder/kotlin/src/main/kotlin/pro/truongsinh/appium_flutter/finder/pageback.kt",
    "chars": 199,
    "preview": "@file:JvmName(\"_FinderRawMethods\")\n@file:JvmMultifileClass\npackage pro.truongsinh.appium_flutter.finder\n\nfun pageBack():"
  },
  {
    "path": "finder/kotlin/src/main/kotlin/pro/truongsinh/appium_flutter/finder/serializer.kt",
    "chars": 1310,
    "preview": "@file:JvmName(\"_FinderRawMethods\")\n@file:JvmMultifileClass\npackage pro.truongsinh.appium_flutter.finder\n\nimport java.uti"
  },
  {
    "path": "finder/kotlin/src/main/kotlin/pro/truongsinh/appium_flutter/finder/text.kt",
    "chars": 237,
    "preview": "@file:JvmName(\"_FinderRawMethods\")\n@file:JvmMultifileClass\npackage pro.truongsinh.appium_flutter.finder\n\nfun text(input:"
  },
  {
    "path": "finder/kotlin/src/test/kotlin/pro/truongsinh/appium_flutter/finder/FinderTest.kt",
    "chars": 3808,
    "preview": "package pro.truongsinh.appium_flutter.finder\n\nimport java.util.regex.Pattern;\n\nimport org.junit.Assert.assertEquals\nimpo"
  },
  {
    "path": "finder/nodejs/LICENSE",
    "chars": 1109,
    "preview": "MIT License\n-----------\n\nCopyright (c) 2019 TruongSinh Tran-Nguyen <i@truongsinh.pro>\nPermission is hereby granted, free"
  },
  {
    "path": "finder/nodejs/README.md",
    "chars": 839,
    "preview": "# Appium Flutter Finder\n\nCompanion `finder` for [Appium Flutter Driver](https://www.npmjs.com/package/appium-flutter-dri"
  },
  {
    "path": "finder/nodejs/lib/base64url.ts",
    "chars": 788,
    "preview": "const W3C_ELEMENT: string = `element-6066-11e4-a52e-4f735466cecf`;\nconst MJSON_ELEMENT: string = `ELEMENT`;\n\nexport cons"
  },
  {
    "path": "finder/nodejs/lib/base64url_test.ts",
    "chars": 1102,
    "preview": "import expect from 'expect';\nimport { decode, encode } from './base64url';\n\ndescribe(`base64url`, () => {\n  it(`decode m"
  },
  {
    "path": "finder/nodejs/lib/deserializer.ts",
    "chars": 160,
    "preview": "import { decode } from './base64url';\n// @todo consider using protobuf\nexport const deserialize = (base64String: string)"
  },
  {
    "path": "finder/nodejs/lib/serializer.ts",
    "chars": 2535,
    "preview": "import { encode } from './base64url';\nimport { deserialize } from './deserializer';\n\n// @todo consider using protobuf\nfu"
  },
  {
    "path": "finder/nodejs/lib/serializer_test.ts",
    "chars": 3717,
    "preview": "// tslint:disable:object-literal-sort-keys\nimport expect from 'expect';\nimport * as find from './serializer';\n\ndescribe("
  },
  {
    "path": "finder/nodejs/package.json",
    "chars": 1070,
    "preview": "{\n  \"name\": \"appium-flutter-finder\",\n  \"description\": \"Finder for Appium Flutter driver\",\n  \"keywords\": [\n    \"appium\",\n"
  },
  {
    "path": "finder/nodejs/tsconfig.json",
    "chars": 277,
    "preview": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@appium/tsconfig/tsconfig.json\",\n  \"compilerOption"
  },
  {
    "path": "finder/nodejs/tslint.json",
    "chars": 195,
    "preview": "{\n    \"defaultSeverity\": \"error\",\n    \"extends\": [\n        \"tslint:recommended\"\n    ],\n    \"jsRules\": {},\n    \"rules\": {"
  },
  {
    "path": "finder/python/.pylintrc",
    "chars": 14659,
    "preview": "[MASTER]\n\n# A comma-separated list of package or module names from where C extensions may\n# be loaded. Extensions are lo"
  },
  {
    "path": "finder/python/README.md",
    "chars": 2022,
    "preview": "# Getting the Appium Flutter Finder\n\nThere are three ways to install and use the Appium Flutter Finder.\nSupported Python"
  },
  {
    "path": "finder/python/appium_flutter_finder/__init__.py",
    "chars": 58,
    "preview": "from .flutter_finder import FlutterElement, FlutterFinder\n"
  },
  {
    "path": "finder/python/appium_flutter_finder/flutter_finder.py",
    "chars": 3120,
    "preview": "import base64\nimport json\n\nfrom appium.webdriver.webelement import WebElement\n\n\nclass FlutterElement(WebElement):\n    pa"
  },
  {
    "path": "finder/python/setup.py",
    "chars": 1445,
    "preview": "#!/usr/bin/env python\n\nimport io\nimport os\n\nfrom setuptools import find_packages, setup\n\nsetup(\n    name='Appium-Flutter"
  },
  {
    "path": "finder/python/tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "finder/python/tests/flutter_finder_tests.py",
    "chars": 3866,
    "preview": "import unittest\n\nimport appium_flutter_finder.flutter_finder as finder\n\n\nclass FlutterFinderTest(unittest.TestCase):\n   "
  },
  {
    "path": "finder/ruby/Gemfile",
    "chars": 86,
    "preview": "source 'https://rubygems.org'\ngemspec\n\ngem 'minitest', '~> 5.0'\ngem 'rake', '~> 13.0'\n"
  },
  {
    "path": "finder/ruby/README.md",
    "chars": 2242,
    "preview": "# AppiumFlutterFinder\n\nRuby finder elements for https://github.com/appium/appium-flutter-driver\n\n## Installation\n\nAdd th"
  },
  {
    "path": "finder/ruby/Rakefile",
    "chars": 195,
    "preview": "require 'bundler/gem_tasks'\nrequire 'rake/testtask'\n\nRake::TestTask.new(:test) do |t|\n  t.libs << 'test'\n  t.libs << 'li"
  },
  {
    "path": "finder/ruby/appium_flutter_finder.gemspec",
    "chars": 1127,
    "preview": "lib = File.expand_path('lib', __dir__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequire 'appium_flutter_f"
  },
  {
    "path": "finder/ruby/lib/appium_flutter_finder/version.rb",
    "chars": 98,
    "preview": "module Appium\n  module Flutter\n    module Finder\n      VERSION = '0.8.0'.freeze\n    end\n  end\nend\n"
  },
  {
    "path": "finder/ruby/lib/appium_flutter_finder.rb",
    "chars": 2982,
    "preview": "require 'json'\nrequire 'base64'\n\nrequire 'appium_lib_core'\nrequire 'appium_flutter_finder/version'\n\nmodule Appium\n  modu"
  },
  {
    "path": "finder/ruby/test/appium_flutter_finder_test.rb",
    "chars": 3434,
    "preview": "require 'test_helper'\nrequire_relative '../lib/appium_flutter_finder'\n\nclass AppiumFlutterFinderTest < Minitest::Test\n  "
  },
  {
    "path": "finder/ruby/test/test_helper.rb",
    "chars": 115,
    "preview": "$LOAD_PATH.unshift File.expand_path('../lib', __dir__)\nrequire 'appium_flutter_finder'\n\nrequire 'minitest/autorun'\n"
  },
  {
    "path": "finder/spec.json",
    "chars": 267,
    "preview": "{\n  \"ancestor\": \"eyJmaW5kZXJUeXBlIjoiQW5jZXN0b3IiLCJtYXRjaFJvb3QiOmZhbHNlLCJvZl9maW5kZXJUeXBlIjoiQnlTZW1hbnRpY3NMYWJlbCI"
  },
  {
    "path": "jitpack.yml",
    "chars": 84,
    "preview": "jdk:\n  - openjdk11\ninstall:\n  - cd finder/kotlin\n  - ./gradlew publishToMavenLocal\n\n"
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the appium-userland/appium-flutter-driver GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 159 files (66.3 MB), approximately 266.3k tokens, and a symbol index with 228 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!