+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/_template__of_JUnit.xml b/.idea/runConfigurations/_template__of_JUnit.xml
new file mode 100644
index 0000000..af97bc0
--- /dev/null
+++ b/.idea/runConfigurations/_template__of_JUnit.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/sonarlint.xml b/.idea/sonarlint.xml
new file mode 100644
index 0000000..dd3e214
--- /dev/null
+++ b/.idea/sonarlint.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 95de60a..a1b558a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,5 @@
-MIT License
-
-Copyright (c) 2025 Money Forward, Inc.
+The MIT License (MIT)
+Original work Copyright (c) 2024 Money Forward, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -8,14 +7,12 @@ 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 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.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9b187c8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,62 @@
+# We use Makefile for simplifying mvn command's usage.
+# Since InspektorDog (shortened "insdog") is a Java-based component, its binary build and release should be done through mvn, without relying on Makefile.
+
+BASH:=$(shell which bash)
+MVN:=source .dependencies/sdkman/bin/sdkman-init.sh && \
+ sdk use java "${SDK_JDK_NAME}" && \
+ mvn -B -Dmaven.javadoc.skip=true
+MVN_WITH_JAVADOC:=source .dependencies/sdkman/bin/sdkman-init.sh && \
+ sdk use java "${SDK_JAVADOC_JDK_NAME}" && \
+ mvn -B -Dmaven.javadoc.skip=false
+PROJ_DIR:=$(shell pwd)
+
+## This is a Makefile for "InspektorDog" project.
+## - https://github.com/moneyforward/insdog/wiki/9-ContributionGuidelines%7CMakefile
+ABOUT: help
+ @echo "__ENV_RC__='${__ENV_RC__}'"
+ :
+
+## Cleans all intermediate files, which should be generated only under `target` directory.
+clean:
+ @$(MVN) clean
+
+## Compiles production source code only
+compile:
+ @$(MVN) clean compile
+
+## Executes unit tests
+test:
+ @$(MVN) clean compile test
+
+## Does "mvn package" without generating JavaDoc.
+package:
+ @$(MVN) clean compile package
+
+## Does "mvn deploy" without generating JavaDoc.
+deploy:
+ @$(MVN) clean compile deploy
+
+## Does "mvn release:prepare" and "mvn release:perform" without generating JavaDoc.
+release:
+ @$(MVN) release:prepare
+ @$(MVN) release:perform
+
+## Generate a site of this product under `target/site` directory.
+site:
+ @$(MVN_WITH_JAVADOC) clean compile site
+ @./src/build_tools/render-md-into-html.sh src/site/markdown target/site src/site/resources/html
+ @./src/build_tools/mangle-javadoc-html-files.sh target/site/en
+
+# Generate Javadoc under `target/site/apidocs` dir.
+# Deprecated. Use `site` instead.
+javadoc: site
+ :
+
+## Build this repository locally.
+## This is a synonym for `package`.
+build: package
+ :
+
+## Show help.
+help:
+ make2help $(MAKEFILE_LIST)
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..33e5025
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,125 @@
+include::attributes.adoc[]
+
+:toc:
+
+== About **InspektorDog**
+
+**InspektorDog** (**InsDog** for short) is a test runner library for developers.
+
+== On-boarding the Project
+
+Check https://github.com/moneyforward/insdog/wiki/developmentGuide[developmentGuide] for more detail.
+
+=== Preparation
+
+Please be sure you are on **macOS**.
+The `bootstrap.sh` script is currently only for **macOS**, although it's not difficult to set up your environment for development.
+
+
+=== Bootstrapping: Setting Up Development Environment for macOS
+
+Clone the repository:
+
+[source, bash]
+----
+$ git clone https://github.com/moneyforward/insdog.git
+----
+
+Then `cd` to the directory:
+
+[source, bash]
+----
+$ cd insdog
+----
+
+In the project root directory, run:
+
+[source, bash]
+----
+$ ./bootstrap.sh
+----
+
+If this step succeeds, the script will print something like following and should exit with `0`.
+
+----
+We are erasing the old .dependencies
+pass:
+---
+FAILED CHECKS: 0
+BEGIN: BOOTSTRAP
+[BOOTSTRAP] Initialized empty Git repository in /Users/your.name/Documents/github/moneyforward/insdog/.dependencies/homebrew/.git/
+
+...
+
+ [sdkman:maven] Installing: maven 3.9.6
+ [sdkman:maven] Done installing!
+ [sdkman:maven]
+ [sdkman:maven]
+ [sdkman:maven] Setting maven 3.9.6 as default.
+END: sdkman:maven
+----
+
+NOTE:: In case you want to re-try the set-up for some reason, in the project root directory please do:
+[source, bash]
+----
+$ sudo rm -fr .dependencies
+----
+Then, do `./bootstrap.sh`.
+
+[[IDE]]
+=== Integrated Development Environment (IDE)
+
+We use *IntelliJ IDEA* for daily development.
+You can use either Community or Ultimate edition.
+For SDET(FW) or SDET(PM), Ultimate edition is recommended.
+
+You can install required the plugins from the `Install required plugins` button at the bottom right when you open *IntelliJ IDEA* without them.
+
+You can just open a test class such as `VisitAllMenuItemsTest.java` and run it with the "Play" button (a green triangle) to run it.
+
+NOTE:: To work with environment variables in IntelliJ IDEA, you need to install the `EnvFile` plugin:
+1. Open IntelliJ IDEA.
+2. Go to `IntelliJ IDEA` > `Settings`.
+3. Navigate to `Plugins`.
+4. Search for `EnvFile` in the Marketplace tab.
+5. Click `Install` to add the plugin.
+6. Restart IntelliJ IDEA if prompted.
+
+=== Playing with `./build.sh`
+
+`build.sh` is a very thin wrapper script of `make` command.
+It only defines environment variables assumed by the targets defined in `Makefile`, then executes `make` command with arguments passed to itself.
+
+You can skip this part and just open the directory to which you cloned the source code with <>.
+
+Still, it is important to define development tasks in the `Makefile` because it allows us to automate and integrate them into CI environment.
+
+
+==== Building the Executable.
+
+Run the following command.
+
+[source, bash]
+----
+$ ./build.sh build
+----
+
+==== Showing a Help
+
+To know what other targets are defined and their usages, just do:
+
+[source, bash]
+----
+$ ./build.sh
+----
+
+This gives you explanations of "public" targets in the `Makefile`, which you are supposed to use in daily developments.
+
+=== Coding Conventions
+
+Coding conventions are defined under `.idea/codeStyles` and `.ideaInspectionProfiles`.
+When you want to propose improvements in the style, please open a pull request containing only files under them.
+
+== Copyright and license
+
+All Rights Reserved by Money Forward.
diff --git a/README.md b/README.md
deleted file mode 100644
index 3174868..0000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# insdog
-A repository for next generation of automated testing tool and framework.
diff --git a/attributes.adoc b/attributes.adoc
new file mode 100644
index 0000000..8e05518
--- /dev/null
+++ b/attributes.adoc
@@ -0,0 +1 @@
+include::src/site/asciidoc/en/attributes.adoc[]
\ No newline at end of file
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 0000000..9224893
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,267 @@
+#!/bin/bash -eu
+
+# Component versions to install
+COMPONENT_VERSIONS="$(cat <<'EOF'
+JDK: 21.0.5-oracle
+JAVADOC_JDK: 25.ea.2-open
+MAVEN: 3.9.6
+GOLANG: 1.21.6
+EOF
+)"
+
+function sed() {
+ /usr/bin/sed "${@}"
+}
+
+# A function to ensure macOS default grep is used.
+function grep() {
+ /usr/bin/grep "${@}"
+}
+
+function projectbrew() {
+ "${brewdir}/bin/brew" "${@}"
+}
+
+function version_for() {
+ local _component_name="${1}"
+ echo "${COMPONENT_VERSIONS}" | grep "^${_component_name}" | cut -f 2 -d ':' | sed -E 's/^ +//g'
+}
+
+function jdk_version() {
+ version_for "JDK"
+}
+function javadoc_jdk_version() {
+ version_for "JAVADOC_JDK"
+}
+function maven_version() {
+ version_for "MAVEN"
+}
+function golang_version() {
+ version_for "GOLANG"
+}
+
+# Reads standard streams (stdout and stderr) and assign the content of them to variables specified by the first and
+# second parameters respectively.
+# Note that this function doesn't declare variables specified by the parameters.
+# They need to be defined in the caller side if necessary.
+# Also note that the behavior is undefined, when a variable name which starts with `__read_std_` is specified.
+# 1: A variable name to which stdout from the parameter 3- will be assigned.
+# 2: A variable name to which stderr from the parameter 3- will be assigned.
+# 3-: A command line to be executed by this function.
+function read_std() {
+ local __read_std_stdout_varname __read_std_stderr_varname
+ local __read_std_tmpfile_stderr __read_std_stdout_content __read_std_stderr_content __read_std_exit_code=0
+ __read_std_stdout_varname="$(printf '%q' "${1}")"
+ __read_std_stderr_varname="$(printf '%q' "${2}")"
+ shift; shift
+ __read_std_tmpfile_stderr="$(mktemp)"
+ __read_std_stdout_content="$("${@}" 2> "${__read_std_tmpfile_stderr}")" || {
+ __read_std_exit_code=$?
+ }
+ __read_std_stdout_content="$(printf '%q' "${__read_std_stdout_content}")"
+ __read_std_stderr_content="$(cat "${__read_std_tmpfile_stderr}")"
+ __read_std_stderr_content="$(printf '%q' "${__read_std_stderr_content}")"
+
+ eval "${__read_std_stdout_varname}=${__read_std_stdout_content}"
+ eval "${__read_std_stderr_varname}=${__read_std_stderr_content}"
+
+ rm -f "${__read_std_tmpfile_stderr}"
+ return "${__read_std_exit_code}"
+}
+
+function __bootstrap__perform_checks() {
+ local _installation_reportdir="${1}" _session_name="${2}"
+ local _failed=0
+ local _stdout _stderr _session_dir="${1}/${_session_name}"
+ shift
+ shift
+ mkdir -p "${_session_dir}"
+ for _i in "${@}"; do
+ mkdir -p "${_session_dir}/${_i}"
+ read_std _stdout _stderr "${_i}" || {
+ echo "${_stdout}" > "${_session_dir}/${_i}/stdout"
+ echo "${_stderr}" > "${_session_dir}/${_i}/stderr"
+ echo "FAIL: <${_i}>" >&2
+ _failed=$((_failed + 1))
+ continue
+ }
+ echo "${_stdout}" > "${_session_dir}/${_i}/stdout"
+ echo "${_stderr}" > "${_session_dir}/${_i}/stderr"
+ echo "pass: <${_i}>" >&2
+ done
+ echo "----"
+ echo "FAILED CHECKS: ${_failed}" >&2
+ [[ 0 == "${_failed}" ]] || return 1
+}
+
+function __bootstrap__checkenv() {
+ local _installation_reportdir="${1}"
+ local _checks=()
+ function is_curl_installed() {
+ which curl
+ }
+ _checks+=("is_curl_installed")
+ function is_git_installed() {
+ which git
+ }
+ _checks+=("is_git_installed")
+ function is_ruby_installed() {
+ which ruby
+ }
+ _checks+=("is_ruby_installed")
+ function is_python3_12_installed() {
+ which python3.12
+ }
+ _checks+=("is_python3_12_installed")
+ __bootstrap__perform_checks \
+ "${_installation_reportdir}" \
+ "pre-check" \
+ "${_checks[@]}"
+}
+
+function install_project_homebrew() {
+ local _homebrew_dir="${1}"
+ mkdir -p "${_homebrew_dir}"
+ git init "${_homebrew_dir}"
+ git -C "${_homebrew_dir}" remote add "origin" https://github.com/Homebrew/brew
+ # git checkout -b main -C "${_homebrew_dir}"
+ curl -L https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 -C "${_homebrew_dir}"
+ # rm -fr "$("${_homebrew_dir}/bin/brew" --repo homebrew/core)"
+
+ git -C "${_homebrew_dir}" add --all .
+ git -C "${_homebrew_dir}" commit -a -m 'DUMMY COMMIT' >& /dev/null
+}
+
+function bootstrap_homebrew() {
+ local _homebrew_dir="${1}"
+ install_project_homebrew "${_homebrew_dir}" | progress "BOOTSTRAP" 2>&1 /dev/null
+}
+
+function install_brew_package() {
+ local _homebrew_dir="${1}" _package="${2}"
+ projectbrew install "${2}" | tee >(progress "${_package}")
+}
+
+function sdk_install() {
+ local _lang="${1}" _ver="${2}"
+ bash -c 'source '"${SDKMAN_DIR}"'/bin/sdkman-init.sh
+ yes | sdk install '"${_lang}"' '"${_ver}"
+}
+
+function progress() {
+ local _item_name="${1}"
+ echo "BEGIN: ${_item_name}" >&2
+ sed -E 's/^(.*)$/ ['"${_item_name}"'] \1/g' >&2
+ echo "END: ${_item_name}" >&2
+}
+
+
+function caveats() {
+ # LIMITATION: Only PATH environment variable mangling will be considered.
+ sed -n '/==> Caveats/,/END/p' | grep 'PATH=' || :
+}
+
+function reset_caveats_rc() {
+ local _fname="${1}"
+ [[ -e "${_fname}" ]] && rm "${_fname}"
+ touch "${_fname}"
+}
+
+function compose_goenv_rc() {
+ local _project_godir="${1}" _go_version="${2}"
+ echo "
+ export GOENV_ROOT=${_project_godir}/env
+ export GOENV_SHELL=bash
+ export GOPATH=${_project_godir}/${_go_version}
+ export GOROOT=${_project_godir}/env/versions/${_go_version}
+
+ export PATH=${_project_godir}/${_go_version}/bin:${PATH}
+ "
+}
+
+function compose_sdk_rc() {
+ local _jdk_name="${1}" _javadoc_jdk_name="${2}"
+ echo "
+ export SDK_JDK_NAME=${_jdk_name}
+ export SDK_JAVADOC_JDK_NAME=${_javadoc_jdk_name}
+ "
+}
+
+function message() {
+ echo "${@}" >&2
+}
+
+function main() {
+ local _projectdir _project_dependencies_dir _project_brewdir _project_rcdir _caveats_file
+ local _project_godir _goenv_file
+ local _project_sdkman_dir
+ _projectdir="$(dirname "${1}")"
+ _projectdir="$(realpath "${_projectdir}")"
+ _project_dependencies_dir="${_projectdir}/.dependencies"
+
+ _project_brewdir="${_project_dependencies_dir}/homebrew"
+
+ _project_rcdir="${_project_dependencies_dir}/rc"
+ _caveats_file="${_project_dependencies_dir}/rc/caveats.rc"
+
+ _goenv_file="${_project_dependencies_dir}/rc/goenv.rc"
+ _project_godir="${_project_dependencies_dir}/go"
+
+ _sdkenv_file="${_project_dependencies_dir}/rc/sdk.rc"
+ _project_sdkman_dir="${_project_dependencies_dir}/sdkman"
+ shift
+
+ local _precheck_reportdir=".dependencies/bootstrap"
+ mkdir -p "${_precheck_reportdir}"
+
+ # Erase all the downloaded files.
+ message "We are erasing the old .dependencies"
+ sudo rm -fr .dependencies
+ # Performs precheck
+ __bootstrap__checkenv "${_precheck_reportdir}"
+
+ mkdir -p "${_project_rcdir}"
+ touch "${_project_rcdir}/.bash_profile" # Ensure the presence of .bash_profile read by env.rc
+ bootstrap_homebrew "${_project_brewdir}"
+ reset_caveats_rc "${_caveats_file}"
+
+ export HOMEBREW_NO_AUTO_UPDATE=0
+ # Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
+ # Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
+ export HOMEBREW_NO_INSTALL_CLEANUP=0
+ export HOMEBREW_NO_ENV_HINTS=0
+
+ # shellcheck disable=SC2129
+ install_brew_package "${_project_brewdir}" make | caveats >> "${_caveats_file}"
+ install_brew_package "${_project_brewdir}" gnu-sed | caveats >> "${_caveats_file}"
+ install_brew_package "${_project_brewdir}" findutils | caveats >> "${_caveats_file}"
+ install_brew_package "${_project_brewdir}" act | caveats >> "${_caveats_file}"
+ install_brew_package "${_project_brewdir}" pandoc | caveats >> "${_caveats_file}"
+
+ # golang
+ mkdir -p "${_project_godir}/env"
+ install_brew_package "${_project_brewdir}" goenv > /dev/null
+ compose_goenv_rc "${_project_godir}" "$(golang_version)" > "${_goenv_file}"
+ # shellcheck disable=SC1090
+ source "${_goenv_file}"
+ "${_project_brewdir}/bin/goenv" install -q "$(golang_version)" 2>&1 | progress "goenv:$(golang_version)"
+ "${_project_godir}/env/versions/$(golang_version)/bin/go" install github.com/Songmu/make2help/cmd/make2help@latest 2>&1 | progress "go:make2help"
+
+ # sdkman & Java
+ export HOME="${_project_rcdir}" # To avoid .bashrc / .bash_profile / .zsh_profile being updated
+ export SDKMAN_DIR="${_project_sdkman_dir}"
+ # install sdkman
+ curl -s "https://get.sdkman.io" | /bin/bash 2>&1 | progress "sdkman"
+
+ sdk_install java "$(javadoc_jdk_version)" | progress "sdkman:java for javadoc"
+ sdk_install java "$(jdk_version)" | progress "sdkman:java"
+ sdk_install maven "$(maven_version)" | progress "sdkman:maven"
+
+ compose_sdk_rc "$(jdk_version)" "$(javadoc_jdk_version)" > "${_sdkenv_file}"
+}
+
+projectdir="$(dirname "${BASH_SOURCE[0]}")"
+projectdir="$(realpath "${projectdir}")"
+brewdir="${projectdir}/.dependencies/homebrew"
+
+main "${0}" "$@"
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..95a8639
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+source "$(dirname "${BASH_SOURCE[0]}")/env.rc"
+
+make "${@}"
\ No newline at end of file
diff --git a/env.rc b/env.rc
new file mode 100644
index 0000000..8eb3610
--- /dev/null
+++ b/env.rc
@@ -0,0 +1,33 @@
+[[ "${__ENV_RC__:-""}" == yes ]] && return 0
+__ENV_RC__=yes
+
+function message() {
+ echo "${@}" >&2
+}
+
+function abort() {
+ message "${@}"
+ exit 1
+}
+
+function init_for_macos() {
+ basedir="$(dirname "${BASH_SOURCE[0]}")"
+ rcdir="${basedir}/.dependencies/rc"
+ [[ -d "${rcdir}" ]] || abort "Please run 'bootstrap.sh', first!"
+
+ export PATH="${basedir}/.dependencies/homebrew/bin:${PATH}"
+ source "${rcdir}/sdk.rc"
+ source "${rcdir}/goenv.rc"
+ source "${rcdir}/caveats.rc"
+ source "${rcdir}/.bash_profile"
+}
+
+function main() {
+ if [[ "${OSTYPE}" == "darwin"* ]]; then
+ init_for_macos "${@}"
+ else
+ message "${OSTYPE}: no user terminal initialization step is defined for this os type. Going ahead."
+ fi
+}
+
+main "${@}"
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..81adf87
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,762 @@
+
+ 4.0.0
+ jp.co.moneyforward.insdog
+ insdog-all
+ jar
+
+ 0.4.3-SNAPSHOT
+ An action-based testing framework and library
+
+ insdog
+ http://moneyforward.github.io/${project.name}
+
+
+ scm:git:git@github.com/moneyforward/${project.name}.git
+
+ scm:git:git@github.com:moneyforward/${project.name}.git
+
+ http://moneyforward.github.io/${project.name}
+ ${project.name}-${project.version}
+
+
+
+
+ Apache License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ Money Forward Corporation
+ http://moneyforward.github.io/
+
+
+
+
+ dakusui
+ Hiroshi Ukai
+ dakusui@gmail.com
+
+
+ rinsyan0518
+ Takamitsu Hamajo
+ rinsyan0518@gmail.com
+
+
+ sukezan
+ Kosuke Yamagami | Kay
+ suke.3gs@icloud.com
+
+
+ kwallaschek
+ Kay Wallaschek
+ kayluis.wallaschek@gmail.com
+
+
+ clover
+ clover-zhao
+ cloverzz521@gmail.com
+
+
+
+
+
+ UTF-8
+ github
+ ${project.basedir}/.generated/build/gems
+
+
+ 1.44.0
+ 4.7.6
+ 4.8.174
+ 2.1.3
+ 6.1.5
+ 1.7.36
+ 2.17.2
+ 2.0.3
+ 1.70
+
+ 6.9.0.202403050737-r
+
+
+ 1.2.0
+ 5.10.1
+ 5.10.1
+ 1.10.2
+
+
+ 2.0.0
+ 2.0.0-RC.1
+ 2.5.11
+ 2.0.1
+ 9.2.12.0
+ 0.8.12
+ 3.2.0
+ 3.3.2
+ 3.3.1
+ 3.13.0
+ 3.3.0
+ 3.2.5
+ 3.3.0
+ 3.6.3
+ 4.0.0-M13
+ 3.5.0
+ 3.3.0
+ 3.1.1
+ 3.1.1
+ 3.0.1
+ 3.5.3
+ 3.1.0
+
+ 0.4.0
+ 24.1.1
+ 5.4.0
+
+
+
+
+
+ com.microsoft.playwright
+ playwright
+ ${playwright.version}
+
+
+ org.slf4j
+ slf4j-simple
+
+
+
+
+ com.eatthepath
+ java-otp
+ ${java-otp.version}
+
+
+ info.picocli
+ picocli
+ ${picocli.version}
+
+
+ org.slf4j
+ slf4j-simple
+
+
+
+
+ org.eclipse.jgit
+ org.eclipse.jgit
+ ${org.eclipse.jgit.version}
+
+
+ io.github.classgraph
+ classgraph
+ ${classgraph.version}
+
+
+ org.slf4j
+ slf4j-simple
+
+
+
+
+ com.github.dakusui
+ osynth
+ ${osynth.version}
+
+
+ com.github.dakusui
+ valid8j
+ ${valid8j.version}
+
+
+ com.github.dakusui
+ actionunit
+ ${actionunit.version}
+
+
+ org.slf4j
+ slf4j-simple
+
+
+
+
+ org.opentest4j
+ opentest4j
+ ${opentest4j.version}
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-jupiter-engine.version}
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-jupiter-api.version}
+
+
+ org.junit.platform
+ junit-platform-launcher
+ ${junit-platform-launcher.version}
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit-jupiter-api.version}
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j-api.version}
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+ ${apache-log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${apache-log4j.version}
+
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ ${apache-log4j.version}
+
+
+
+ org.mockito
+ mockito-core
+ ${mockito-core.version}
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ ${maven-clean-plugin.version}
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ ${maven-resources-plugin.version}
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ ${exec-maven-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+ copy-resources
+ process-resources
+
+ resources
+
+
+
+
+ src/main/java
+
+ **/*.js
+
+ .
+
+
+ src/site/asciidoc
+
+ **/*.adoc
+
+ ${project.build.directory}/site/
+
+
+ ${project.build.outputDirectory}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${maven-source-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ -ea ${surefireArgLine} --add-opens java.base/java.lang.invoke=ALL-UNNAMED
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ ${maven-failsafe-plugin.version}
+
+ -ea ${failsafeArgLine} --add-opens java.base/java.lang.invoke=ALL-UNNAMED
+
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-maven-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${maven-javadoc-plugin.version}
+
+
+ attach-javadocs
+ pre-site
+
+ jar
+
+
+
+
+ src/main/javadoc/overview.md
+
+ --add-script=${project.basedir}/src/site/resources/js/mermaid.min.js
+ --add-script=${project.basedir}/src/site/resources/js/mermaid-init.js
+
+
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+ ${maven-site-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+ ${maven-project-info-reports-plugin.version}
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven-jar-plugin.version}
+
+
+
+ jp.co.moneyforward.autotest
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ ${maven-shade-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ ${maven-install-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ ${maven-deploy-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+ ${maven-release-plugin.version}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 21
+ 21
+
+ -Aproject=${project.groupId}/${project.artifactId}
+
+
+
+ info.picocli
+ picocli-codegen
+ ${picocli.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ attach-sources
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-maven-plugin.version}
+
+
+
+ pre-unit-tests
+
+ prepare-agent
+
+
+ ${project.build.directory}/jacoco/jacoco-unit-tests.exec
+ surefireArgLine
+
+
+
+
+ pre-integration-test
+
+ prepare-agent-integration
+
+
+ ${project.build.directory}/jacoco/jacoco-integration-tests.exec
+ failsafeArgLine
+
+
+
+
+ merge-results
+ verify
+
+ merge
+
+
+
+
+ ${project.build.directory}/jacoco
+
+ *.exec
+
+
+
+ ${project.build.directory}/jacoco/jacoco-merged.exec
+
+
+
+
+ post-merge-report
+ verify
+
+ report
+
+
+ ${project.build.directory}/jacoco/jacoco-merged.exec
+ ${project.reporting.outputDirectory}/jacoco
+
+ jp/co/moneyforward/autotest/examples/**
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ remove diag-*.png files
+ clean
+
+ exec
+
+
+ bash
+
+
+ ${basedir}/src/build_tools/remove-adoc-diags.sh
+
+
+
+
+
+ remove package-info.java files
+ clean
+
+ exec
+
+
+ bash
+
+
+ ${basedir}/src/build_tools/remove-package-info-java.sh
+
+
+
+
+
+ package-info.java generation
+ generate-sources
+
+ exec
+
+
+ bash
+
+
+ ${basedir}/src/build_tools/package-info-md_to_package-info-java.sh
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ attach-javadocs-for-package
+ package
+
+ jar
+
+
+
+
+
+ org.asciidoctor
+ asciidoctor-maven-plugin
+ ${asciidoctor.maven.plugin.version}
+
+
+
+ org.jruby
+ jruby-complete
+ ${jruby.version}
+
+
+
+ org.asciidoctor
+ asciidoctorj
+ ${asciidoctorj.version}
+
+
+ org.asciidoctor
+ asciidoctorj-diagram
+ ${asciidoctorj.diagram.version}
+
+
+
+ src/site/asciidoc/en
+
+ asciidoctor-diagram
+
+
+
+
+
+
+
+
+
+ generate-html-doc
+ site
+
+ process-asciidoc
+
+
+ html5
+ target/site/en
+
+ .
+ left
+ font
+ true
+
+ -
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+
+
+
+ org.jruby
+ jruby-complete
+ ${jruby.version}
+
+
+ org.asciidoctor
+ asciidoctor-maven-plugin
+ ${asciidoctor.maven.plugin.version}
+
+
+
+
+ true
+ true
+ false
+ en
+ UTF-8
+ UTF-8
+ false
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+
+ -Dgpg.passphrase=${gpg.passphrase}
+
+
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+
+ report
+
+
+
+
+ ${project.build.directory}/jacoco/jacoco-merged.exec
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+
+ javadoc
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+
+
+
+
+ github-mavenpkg
+ https://maven.pkg.github.com/moneyforward/mavenpkg
+
+
+
+
+
+ mac
+
+
+ mac
+
+
+
+
+ unix
+
+
+ unix
+ Linux
+
+
+
+
+ release-sign-artifacts
+
+
+ performRelease
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${maven-gpg-plugin.version}
+
+ ${gpg.passphrase}
+
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+
+
diff --git a/src/build_tools/convert_links.lua b/src/build_tools/convert_links.lua
new file mode 100644
index 0000000..558b331
--- /dev/null
+++ b/src/build_tools/convert_links.lua
@@ -0,0 +1,6 @@
+function Link(el)
+ if el.target:match("%.md$") then
+ el.target = el.target:gsub("%.md$", ".html")
+ end
+ return el
+end
diff --git a/src/build_tools/lib/mvn-utils.rc b/src/build_tools/lib/mvn-utils.rc
new file mode 100644
index 0000000..58d5090
--- /dev/null
+++ b/src/build_tools/lib/mvn-utils.rc
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+unset -f MVN_UTILS_TMP_DIR
+readonly MVN_UTILS_TMP_DIR="${TMPDIR:-/tmp}/.mvn-utils"
+
+function mvn_init() {
+ if [[ ! -d "${MVN_UTILS_TMP_DIR}" ]]; then
+ mkdir -p "${MVN_UTILS_TMP_DIR}"
+ fi
+}
+
+function project_name() {
+ xmllint --xpath '/project/name/text()' <(sed -E 's/xmlns=[^ ]+//g' pom.xml)
+}
+
+function _compose_mvn_command_line() {
+ local _artifact="${1}" _basedir="${2}"
+ echo mvn -B -Dstyle.color=never dependency:unpack -Dartifact="${_artifact}" \
+ -Dproject.basedir="${_basedir}" \
+ -DoutputDirectory="${_basedir}/out"
+}
+
+function _exec_mvn() {
+ local _artifact="${1}" _basedir="${2}" _stdout="${3}"
+ local _mvn_commandline
+ _mvn_commandline="$(_compose_mvn_command_line "${_artifact}" "${_basedir}")"
+ bash -c "${_mvn_commandline}" >"${_stdout}" || {
+ local _exit_code="$?"
+ tail --lines=20 "${_stdout}"
+ echo
+ echo "ERROR: Find more detail in '${_stdout}'." >&2
+ return ${_exit_code}
+ }
+}
+
+function mvn-find() {
+ local _artifact="${1}"
+ shift
+ local _basedir _outdir _stdout _mvn_commandline _pwd _exit_code=0
+
+ mvn_init
+ _basedir="./target"
+ _stdout="${_basedir}/stdout"
+ _outdir="${_basedir}/out"
+ _exec_mvn "${_artifact}" "${_basedir}" "${_stdout}" || {
+ local _exit_code="${?}"
+ return "${_exit_code}"
+ }
+ _pwd="$(pwd)"
+ cd "${_outdir}" || return 1
+ find . "${@}" || {
+ echo "ERROR: command line: find . $*" >&2
+ _exit_code="${?}"
+ }
+ cd "${_pwd}" || return 1
+ return "${_exit_code}"
+}
+
+function mvn-unpack() {
+ local _artifact="${1}"
+ local _dir="${2}"
+ shift
+ local _basedir _outdir _stdout _mvn_commandline _pwd _exit_code=0
+
+ mvn_init
+ _basedir="./target"
+ _stdout="${_basedir}/stdout"
+ _exec_mvn "${_artifact}" "${_basedir}" "${_stdout}" || {
+ local _exit_code="${?}"
+ return "${_exit_code}"
+ }
+ _pwd="$(pwd)"
+ cp -r "${_basedir}/out/"* "${_dir}/"
+}
diff --git a/src/build_tools/mangle-javadoc-html-files.sh b/src/build_tools/mangle-javadoc-html-files.sh
new file mode 100755
index 0000000..6cb4bd0
--- /dev/null
+++ b/src/build_tools/mangle-javadoc-html-files.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+set -E -o nounset -o errexit +o posix -o pipefail
+shopt -s inherit_errexit
+
+# This is a script to mangle HTML files generated by JavaDoc (Java 25 EA).
+# The JS file for mermaid downloaded from cds.jsdelivr.net doesn't work as of Dec/2024.
+# Also, the JavaDoc embeds am info string in a source file with a prefix `language-`, which disturbs the mermaid.min.js script.
+# To address this problem, this script does a couple of things.
+# 1. It replaces all the occurrences of `class="language-mermaid"` with `class=mermaid`
+# 2. It replaces all the occurrences of `../ ... ../mermaid.min.js` with the URL from which it was downloaded originally.
+function main() {
+ local _target_dir="${1}"
+ # Taking care of JavaDoc generated HTML
+ find "${_target_dir}" -type f \
+ -name '*.html' \
+ -exec sed -i -E 's/class=\"language-mermaid\"/class=\"mermaid\"/g' {} \;
+ # Taking care of JavaDoc generated HTML
+ find "${_target_dir}" -type f \
+ -name '*.html' \
+ -exec sed -i -E 's!(\.\./)+script-files/mermaid.min.js!https://cdn.jsdelivr.net/npm/mermaid@11.4.1/dist/mermaid.min.js!g' {} \;
+ # Taking care of pandoc generated HTML
+ find "${_target_dir}" -type f \
+ -name '*.html' \
+ -exec sed -i -E 's!
!
!g' {} \;
+}
+
+main "${@}"
diff --git a/src/build_tools/package-info-md_to_package-info-java.sh b/src/build_tools/package-info-md_to_package-info-java.sh
new file mode 100755
index 0000000..d7e6079
--- /dev/null
+++ b/src/build_tools/package-info-md_to_package-info-java.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+set -eu
+
+function convert_package_info_md_to_package_info_java() {
+ local _base_dir=$1
+ local _rel_dir=$2
+ local _i
+ local _out="${_base_dir}/${_rel_dir}/package-info.java"
+ rm -f ${_out}
+ touch ${_out}
+ echo "///" >> ${_out}
+ while IFS= read -r _i
+ do
+ echo "/// ${_i}" >> ${_out}
+ done < "${_base_dir}/${_rel_dir}/package-info.md"
+
+ echo "/// " >> ${_out}
+ echo "package ${_rel_dir//\//.};" >> ${_out}
+}
+
+# find src/main/java -name 'package-info.md' -exec echo convert_package_info_md_to_package_info_java src/main/java
+# convert_package_info_md_to_package_info_java /Users/hiroshi.ukai/Documents/scriptiveunit/src/main/java com/github/dakusui/scriptiveunit/utils
+
+base=src/main/java
+for i in $(find ${base} -name 'package-info.md'); do
+ convert_package_info_md_to_package_info_java ${base} $(dirname ${i#${base}/})
+done;
\ No newline at end of file
diff --git a/src/build_tools/remove-adoc-diags.sh b/src/build_tools/remove-adoc-diags.sh
new file mode 100644
index 0000000..da0ccb6
--- /dev/null
+++ b/src/build_tools/remove-adoc-diags.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+set -eu
+
+rm diag-*.png >& /dev/null || echo "No file to remove. Let's go ahead." >&2
\ No newline at end of file
diff --git a/src/build_tools/remove-package-info-java.sh b/src/build_tools/remove-package-info-java.sh
new file mode 100644
index 0000000..f1ddb89
--- /dev/null
+++ b/src/build_tools/remove-package-info-java.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+set -eu
+
+find src/main -name 'package-info.java' -exec rm {} \;
\ No newline at end of file
diff --git a/src/build_tools/render-md-into-html.sh b/src/build_tools/render-md-into-html.sh
new file mode 100755
index 0000000..fd1c888
--- /dev/null
+++ b/src/build_tools/render-md-into-html.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+set -E -o nounset -o errexit +o posix -o pipefail
+shopt -s inherit_errexit
+
+main() {
+ local _indir="${1}" _outdir="${2}" _resources_dir="${3}"
+ local _i
+ local _convert_links
+ _convert_links="$(dirname "${0}")/convert_links.lua"
+ for _i in $(find "${_indir}" -type f -name '*.md' -printf "%P\n"); do
+ local _outfile
+ _outfile="${_outdir}/$(echo "${_i}"|sed -E "s/.md$/.html/g")"
+ mkdir -p "$(dirname "${_outdir}/${_i}")"
+ cat "${_resources_dir}/header.html" > "${_outfile}"
+ pandoc --lua-filter="${_convert_links}" --from markdown --to html "${_indir}/${_i}" >> "${_outfile}"
+ cat "${_resources_dir}/footer.html" >> "${_outfile}"
+ done
+}
+main "${@}"
\ No newline at end of file
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/Click.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/Click.java
new file mode 100644
index 0000000..13ae11a
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/Click.java
@@ -0,0 +1,39 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.github.valid8j.pcond.forms.Printables;
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.Page;
+import jp.co.moneyforward.autotest.framework.core.ExecutionEnvironment;
+
+import java.util.function.Function;
+
+///
+/// An act that models a user behavior, which clicks a specified element.
+///
+///
+///
+///
+public class Click extends ClickBase {
+ ///
+ /// Creates an object of this class.
+ /// @param selector A selector to designate an element to click.
+ ///
+ public Click(String selector) {
+ this(Printables.function("@" + selector, p -> p.locator(selector)));
+ }
+
+ ///
+ /// Creates an objct of this class.
+ ///
+ /// @param locatorFunction A locator for an element to click.
+ ///
+ public Click(Function locatorFunction) {
+ super(locatorFunction);
+ }
+
+ @Override
+ public Page perform(Page page, ExecutionEnvironment executionEnvironment) {
+ this.locatorFunction.apply(page).click();
+ return page;
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/ClickBase.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/ClickBase.java
new file mode 100644
index 0000000..3ef5c95
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/ClickBase.java
@@ -0,0 +1,35 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.Page;
+import jp.co.moneyforward.autotest.framework.action.Act;
+import jp.co.moneyforward.autotest.framework.utils.InternalUtils;
+
+import java.util.function.Function;
+
+///
+/// An abstract base class for clicking acts.
+///
+public abstract class ClickBase implements Act {
+ final Function locatorFunction;
+
+ ///
+ /// Creates an object of this class.
+ ///
+ /// @param locatorFunction A function to locate an element to click.
+ ///
+ protected ClickBase(Function locatorFunction) {
+ this.locatorFunction = locatorFunction;
+ }
+
+ ///
+ /// Returns a name of this object.
+ /// The returned name is printed in action trees.
+ ///
+ /// @return A name of this object.
+ ///
+ @Override
+ public String name() {
+ return InternalUtils.simpleClassNameOf(this.getClass()) + "[" + this.locatorFunction + "]";
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/ClickIfPresent.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/ClickIfPresent.java
new file mode 100644
index 0000000..6c67b93
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/ClickIfPresent.java
@@ -0,0 +1,34 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.Page;
+import jp.co.moneyforward.autotest.framework.core.ExecutionEnvironment;
+
+import java.util.function.Function;
+
+///
+/// A class that represents an act, where:
+///
+/// * Click a specified element exists in a page if it exists.
+/// * Otherwise, does nothing.
+///
+/// Check for presence is done by `Locator#isVisible`.
+///
+public class ClickIfPresent extends ClickBase {
+ ///
+ /// Creates an object of this class.
+ ///
+ /// @param locatorFunction A function to locate an element to be clicked by this object on `perform` method's call.
+ ///
+ public ClickIfPresent(Function locatorFunction) {
+ super(locatorFunction);
+ }
+
+ @Override
+ public Page perform(Page value, ExecutionEnvironment executionEnvironment) {
+ Locator targetElement = this.locatorFunction.apply(value);
+ if (targetElement.isVisible())
+ targetElement.click();
+ return value;
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/CloseBrowser.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/CloseBrowser.java
new file mode 100644
index 0000000..e224585
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/CloseBrowser.java
@@ -0,0 +1,13 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.microsoft.playwright.Browser;
+import jp.co.moneyforward.autotest.framework.action.Act;
+import jp.co.moneyforward.autotest.framework.core.ExecutionEnvironment;
+
+public class CloseBrowser implements Act {
+ @Override
+ public Void perform(Browser value, ExecutionEnvironment executionEnvironment) {
+ value.close();
+ return null;
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/CloseWindow.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/CloseWindow.java
new file mode 100644
index 0000000..08f2afa
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/CloseWindow.java
@@ -0,0 +1,13 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.microsoft.playwright.Playwright;
+import jp.co.moneyforward.autotest.framework.action.Act;
+import jp.co.moneyforward.autotest.framework.core.ExecutionEnvironment;
+
+public class CloseWindow implements Act {
+ @Override
+ public Void perform(Playwright value, ExecutionEnvironment executionEnvironment) {
+ value.close();
+ return null;
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/LocatorFunctions.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/LocatorFunctions.java
new file mode 100644
index 0000000..301953a
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/LocatorFunctions.java
@@ -0,0 +1,88 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.github.valid8j.pcond.forms.Printables;
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.options.AriaRole;
+
+import java.util.function.Function;
+
+import static com.github.valid8j.classic.Requires.requireNonNull;
+
+///
+/// A class that defines function returning utility methods.
+///
+/// Utility methods in this class return a function whose parameter is `Locator`.
+/// Typically, they are used in combination with methods in `PageFunctions`.
+///
+/// For instance,
+///
+/// ```
+/// new Click(locatorBySelector("#js-sidebar-opener").andThen(byText(sideMenuItem)))
+/// ```
+///
+/// This finds an element (locator) found by another function `PageFunctions#locatorBySelector(String)`, and then find an
+/// element whose text is equal to `sideMenuItem`.
+///
+/// Functions returned by methods in this class can be pretty printed on a call of `toString` method call.
+///
+/// @see PageFunctions
+///
+public enum LocatorFunctions {
+ ;
+
+ ///
+ /// A method to compose a function that returns an element at `i` th position in the element (locator) given as a parameter.
+ ///
+ /// @param i An index of an element to be returned.
+ /// @return A function that returns `i` th element in the given locator.
+ ///
+ public static Function nth(int i) {
+ return Printables.function("nth[" + i + "]", l -> l.nth(i));
+ }
+
+ ///
+ /// Returns a function that finds an element whose text matches `text`, under a given `locator`.
+ ///
+ /// The returned function resolves a locator using `Locator#getByText(String)` method.
+ ///
+ /// @param text A string to be matched with the text of a given element.
+ /// @return A function that returns a locator which matches a given `text` under a locator.
+ ///
+ public static Function byText(String text) {
+ requireNonNull(text);
+ return Printables.function("byText[" + text + "]", l -> l.getByText(text));
+ }
+
+
+ ///
+ /// Returns a function that finds an element whose name matches `name`, under a given `locator`.
+ ///
+ /// The returned function resolves a locator using the following approach.
+ ///
+ /// ```java
+ /// (Locator l) -> l.getByRole(AriaRole.LINK,
+ /// new Locator.GetByRoleOptions().setName(name)
+ /// .setExact(!lenient))
+ /// ```
+ ///
+ /// @param name A string to be matched with the text of a given element.
+ /// @param lenient `true` - Make the search lenient / `false` - Make the search strict.
+ /// @return A function that returns a locator which matches a given `text` under a locator.
+ ///
+ public static Function byName(String name, boolean lenient) {
+ requireNonNull(name);
+ return Printables.function("@[name" + (lenient ? "~" : "=") + name + "]",
+ l -> l.getByRole(AriaRole.LINK,
+ new Locator.GetByRoleOptions().setName(name)
+ .setExact(!lenient)));
+ }
+
+ ///
+ /// Returns a function which gives a text content of an element given as an argument.
+ ///
+ /// @return A function which gives a text content of an element given as an argument.
+ ///
+ public static Function textContent() {
+ return Printables.function("textContent", Locator::textContent);
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/Navigate.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/Navigate.java
new file mode 100644
index 0000000..e90894b
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/Navigate.java
@@ -0,0 +1,26 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.microsoft.playwright.Page;
+import jp.co.moneyforward.autotest.framework.action.Act;
+import jp.co.moneyforward.autotest.framework.core.ExecutionEnvironment;
+
+import static com.github.valid8j.classic.Requires.requireNonNull;
+
+public class Navigate implements Act {
+ private final String url;
+
+ public Navigate(String url) {
+ this.url = requireNonNull(url);
+ }
+
+ @Override
+ public Page perform(Page page, ExecutionEnvironment executionEnvironment) {
+ page.navigate(this.url);
+ return page;
+ }
+
+ @Override
+ public String name() {
+ return this.getClass().getSimpleName() + "[" + this.url + "]";
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/PageAct.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/PageAct.java
new file mode 100644
index 0000000..1ef3d88
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/PageAct.java
@@ -0,0 +1,77 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.microsoft.playwright.Page;
+import jp.co.moneyforward.autotest.framework.action.Act;
+import jp.co.moneyforward.autotest.framework.core.ExecutionEnvironment;
+
+import java.util.function.BiConsumer;
+
+import static com.github.valid8j.classic.Requires.requireNonNull;
+
+///
+/// A general-purpose act.
+/// Convenient starting point for writing **insdog** based tests.
+///
+public abstract class PageAct implements Act {
+ private final String description;
+
+ ///
+ /// Creates a new instance of this class.
+ ///
+ /// It is advised to give a concise and descriptive string to `description` parameter as it is printed the test report.
+ /// The `description` should be concise but informative enough for a reader to reproduce the same action that this `Act` performs.
+ ///
+ /// @param description A string that describes this object.
+ ///
+ protected PageAct(String description) {
+ this.description = requireNonNull(description);
+ }
+
+ ///
+ /// Creates a `PageAct` with a given description and an action
+ ///
+ /// @param description A string to describe created page act.
+ /// @param action An action to be performed.
+ /// @return A page act that performs `action`.
+ ///
+ public static PageAct pageAct(String description, BiConsumer action) {
+ return new PageAct(description) {
+ @Override
+ protected void action(Page page, ExecutionEnvironment executionEnvironment) {
+ action.accept(page, executionEnvironment);
+ }
+ };
+ }
+
+ ///
+ /// Performs an action defined for this class.
+ /// Its execution is delegated to `perform(Page,ExecutionEnvironment)` method.
+ ///
+ /// @param value A page object on which this `act` is performed.
+ /// @param executionEnvironment An execution environment, in which this act is performed.
+ /// @return The `value` itself should be returned, usually.
+ ///
+ @Override
+ public Page perform(Page value, ExecutionEnvironment executionEnvironment) {
+ this.action(value, executionEnvironment);
+ return value;
+ }
+
+ ///
+ /// A method that defines the `act` to be performed by this object.
+ ///
+ /// @param page A page object on which this `act` is performed.
+ /// @param executionEnvironment An execution environment, in which this act is performed.
+ ///
+ protected abstract void action(Page page, ExecutionEnvironment executionEnvironment);
+
+ ///
+ /// Returns a name of this object.
+ ///
+ /// @return A name of this object.
+ ///
+ @Override
+ public String name() {
+ return "Page[" + this.description + "]";
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/PageFunctions.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/PageFunctions.java
new file mode 100644
index 0000000..8c22924
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/PageFunctions.java
@@ -0,0 +1,198 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.github.valid8j.pcond.forms.Printables;
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.Page;
+import com.microsoft.playwright.options.AriaRole;
+
+import java.util.function.Function;
+
+import static com.github.valid8j.classic.Requires.requireNonNull;
+
+///
+/// A utility class to handle `Page` object.
+///
+/// Methods in this class return a function whose parameter is a `Page` object of **Playwright**.
+///
+/// It is common to see a situation, where a single method call to a page object cannot determine a single element to be returned.
+/// In such a case, you can use functions provided by `LocatorFunctions` in combination.
+///
+/// ```
+/// new Click(PageFunctions.locatorBySelector("#js-sidebar-opener").andThen(LocatorFunctions.byText(sideMenuItem)))
+/// ```
+///
+/// This is an example to find a locator, which is specified by a text `sideMenuItem` under a locator specified by a selector
+/// string `#js-sidebar-opener`.
+///
+/// In general methods in this class are named in the following manner.
+///
+/// ```
+/// {typeName}By{SelectionMethod}
+/// ```
+///
+/// `typeName` can be, for instance, `locator`, `linkLocator`.
+/// `SelectionMethod` can be `Name`, `Text`, `Label`, `Selector`, etc.
+///
+/// Functions returned by methods in this class can be pretty printed on a call of `toString` method call.
+///
+/// @see LocatorFunctions
+///
+public enum PageFunctions {
+ ;
+
+ ///
+ /// Returns a function that resolves a locator specified by `name` in a given `Page` object.
+ ///
+ /// This is a shorthand method for `linkLocatorByName(name, false)`.
+ ///
+ /// @param name A name of a link.
+ /// @return A function that resolves a locator specified by `name` in a given `Page` object.
+ ///
+ public static Function linkLocatorByName(String name) {
+ return linkLocatorByName(name, false);
+ }
+
+ ///
+ /// Returns a function that resolves a given `name` to a locator of a link whose name matches with it.
+ ///
+ /// @param name A name of a link locator to be matched.
+ /// @param lenient `true` - partial match / `false` - exact match.
+ /// @return A function that resolves a given `name` to a locator of a link whose name matches with it.
+ ///
+ public static Function linkLocatorByName(String name, boolean lenient) {
+ return Printables.function("link[name" + (lenient ? "~" : "=") + name + "]",
+ p -> p.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setExact(!lenient)
+ .setName(name)));
+ }
+
+ ///
+ /// Returns a function that resolves a locator whose text contains `text` in a given page.
+ ///
+ /// @param text A text to be contained by the matching locator.
+ /// @return a function that resolves a locator whose text contains `text` in a given page.
+ ///
+ public static Function locatorByText(String text) {
+ return locatorByText(text, false);
+ }
+
+ ///
+ /// Returns a function that resolves a locator which matches `text` in a given `Page` object.
+ /// If `lenient` is `true`, a locator whose text contains it is considered matched.
+ /// If `lenient` is `false`, a locator whose text equals to `text` is considered matched.
+ ///
+ /// The match is done by:
+ ///
+ /// ```Page#GetByText(text, new Page.GetByTextOptions().setExact(!lenient)```
+ ///
+ /// @param text A text to be matched.
+ /// @param lenient `true` - lenient / `false` - strict.
+ /// @return A function that resolves a locator which matches `text` in a given `Page` object.
+ ///
+ public static Function locatorByText(String text, boolean lenient) {
+ return Printables.function("@[text" + (lenient ? "~" : "=") + text + "]",
+ p -> p.getByText(text, new Page.GetByTextOptions().setExact(!lenient)));
+ }
+
+ ///
+ /// Returns a function that resolve a locator to a button object in a `Page`, whose name is equal to `name`.
+ ///
+ /// @param name A string to be matched with a locator's name.
+ /// @return A function that resolve a locator to a button object in a `Page`, whose name is equal to `name`.
+ ///
+ public static Function buttonLocatorByName(String name) {
+ return Printables.function("@[name=" + name + "]",
+ p -> p.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(name)
+ .setExact(true)));
+ }
+
+
+ ///
+ /// Returns a function that resolves a locator whose label matches with `label` in a given `Page`.
+ ///
+ /// @param label A string to be matched with a label of a locator.
+ /// @return A function that resolves a locator whose label matches with `label`.
+ ///
+ public static Function locatorByLabel(String label) {
+ return locatorByLabel(label, false);
+ }
+
+ ///
+ /// Returns a function that resolves a locator whose label matches with `label` in a given `Page`.
+ ///
+ /// The resolution is done by a following method-call.
+ ///
+ /// ```java
+ /// Page#getByLabel(label, new Page.GetByLabelOptions().setExact(!lenient))
+ /// ```
+ ///
+ /// If `lenient`is set to `true`, a locator whose label contains `label` will be considered matched.
+ /// If it is `false`, a locator whose label is equal to `label` will be considered matched.
+ ///
+ /// @param label A string to be matched with a label of a locator.
+ /// @param lenient `true` - lenient / `false` - strict.
+ /// @return a function that resolves a locator whose label matches with `label` in a given `Page`.
+ ///
+ public static Function locatorByLabel(String label, boolean lenient) {
+ return Printables.function("@[label" + (lenient ? "~" : "=") + label + "]",
+ p -> p.getByLabel(label, new Page.GetByLabelOptions().setExact(!lenient)));
+ }
+
+
+ ///
+ /// Returns a function that resolves a locator whose placeholder is `placeholder`.
+ ///
+ /// @param placeholder A string to be matched with a locator's placeholder.
+ /// @return A function that resolves a locator whose placeholder is `placeholder`.
+ ///
+ public static Function locatorByPlaceholder(String placeholder) {
+ return Printables.function("@[placeholder=" + placeholder + "]",
+ p -> p.getByPlaceholder(placeholder, new Page.GetByPlaceholderOptions().setExact(true)));
+ }
+
+ ///
+ /// Returns a function that resolves a locator specified by `selector` in a given `Page`.
+ ///
+ /// @param selector A selector string that specifies a locator.
+ /// @return A function that resolves a locator specified by `selector` in a given `Page`.
+ ///
+ public static Function locatorBySelector(String selector) {
+ requireNonNull(selector);
+ return Printables.function("@[" + selector + "]", p -> p.locator(selector));
+ }
+
+ ///
+ /// Returns a function that gives a locator of a link whose text contains a given `text` inside a `Page`.
+ ///
+ /// @param text A string to be matched with a text of a link in a given `Page.
+ /// @return A function that gives a locator of a link whose text matches a given `text` inside a `Page`.
+ ///
+ public static Function linkLocatorByText(String text) {
+ return linkLocatorByText(text, true);
+ }
+
+ ///
+ /// Returns a function that gives a locator of a link whose text equals to a given `text` inside a `Page`.
+ ///
+ /// @param text A string to be matched with a text of a link in a given `Page.
+ /// @return A function that gives a locator of a link whose text equals to a given `text` inside a `Page`.
+ ///
+ public static Function linkLocatorByExactText(String text) {
+ return linkLocatorByText(text, false);
+ }
+
+ ///
+ /// Returns a function that gives a title of the given page.
+ ///
+ /// @return A function that gives a title of the given page.
+ ///
+ public static Function toTitle() {
+ return Printables.function("title", Page::title);
+ }
+
+ public static Function linkLocatorByText(String text, boolean lenient) {
+ return Printables.function("link:@[text" + (lenient ? "~" : "=") + text + "]",
+ p -> p.getByRole(AriaRole.LINK,
+ new Page.GetByRoleOptions().setName(text)
+ .setExact(!lenient)));
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/Screenshot.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/Screenshot.java
new file mode 100644
index 0000000..2874e20
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/Screenshot.java
@@ -0,0 +1,34 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.microsoft.playwright.Page;
+import jp.co.moneyforward.autotest.framework.action.Act;
+import jp.co.moneyforward.autotest.framework.core.ExecutionEnvironment;
+
+///
+/// An act that does screenshot.
+/// The browser screenshot is saved under `ExecutionEnvironment#testOutputFilenameFor("screenshot-{stepName}.png")`, where
+/// `{stepName}` is one of `beforeAll`, `beforeEach`, `afterEach`, or `afterAll`.
+///
+/// @see ExecutionEnvironment#testOutputFilenameFor(String)
+///
+public class Screenshot implements Act {
+ ///
+ /// Creates an instance of this class.
+ ///
+ public Screenshot() {
+ // Make default constructor findable.
+ }
+
+ ///
+ /// Performs the screenshot action.
+ ///
+ /// @param value A page for which screenshot is executed.
+ /// @param executionEnvironment An execution environment.
+ /// @return The page object itself given as `value` parameter.
+ ///
+ @Override
+ public Page perform(Page value, ExecutionEnvironment executionEnvironment) {
+ value.screenshot(new Page.ScreenshotOptions().setPath(executionEnvironment.testOutputFilenameFor(String.format("screenshot-%s.png", executionEnvironment.stepName()))));
+ return value;
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/SendKey.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/SendKey.java
new file mode 100644
index 0000000..f21e39b
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/SendKey.java
@@ -0,0 +1,82 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.github.valid8j.pcond.forms.Printables;
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.Page;
+import jp.co.moneyforward.autotest.framework.action.Act;
+import jp.co.moneyforward.autotest.framework.core.ExecutionEnvironment;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import static com.github.valid8j.classic.Requires.requireNonNull;
+
+///
+/// A class that represents an action to send key sequence to a specified locator.
+///
+/// If the key sequence starts with `SendKey.MASK_PREFIX`, the part after it will be sent to an element specified by a `locatorFunction` in a given page,
+/// while the part before it (i.e., the `MASK_PREFIX` itselft) will be printed in the log.
+/// This feature is useful for secret strings to be sent to elements, but not to be printed such as passwords.
+///
+///
+public class SendKey implements Act {
+ ///
+ /// A prefix to control a
+ ///
+ public static final String MASK_PREFIX = "MASK!";
+ private final Supplier keySequenceGenerator;
+ private final Function locatorFunction;
+
+ ///
+ /// Creates an instance of this class.
+ ///
+ /// @param selector A selector string.
+ /// @param keys Keys to be sent to a locator chosen by a `selector` string.
+ /// @see SendKey#SendKey(Function, String)
+ ///
+ public SendKey(String selector, String keys) {
+ this(Printables.function("@" + selector, (Page p) -> p.locator(selector)), keys);
+ }
+
+ ///
+ /// Creates an instance of this class.
+ ///
+ /// @param locatorFunction A function to choose a locator from a given page.
+ /// @param keys Keys to be sent to a chosen locator.
+ ///
+ public SendKey(Function locatorFunction, String keys) {
+ this(locatorFunction, toSupplier(requireNonNull(keys)));
+ }
+
+ ///
+ /// Creates an instance of this class.
+ ///
+ /// @param locatorFunction A function to choose a locator from a given page.
+ /// @param keySequenceGenerator A supplier that generates a key sequence to be sent to a chosen locator.
+ ///
+ public SendKey(Function locatorFunction, Supplier keySequenceGenerator) {
+ this.locatorFunction = requireNonNull(locatorFunction);
+ this.keySequenceGenerator = requireNonNull(keySequenceGenerator);
+ }
+
+ @Override
+ public Page perform(Page value, ExecutionEnvironment executionEnvironment) {
+ this.locatorFunction.apply(value).focus();
+ String keys = this.keySequenceGenerator.get();
+ value.keyboard().type(keys.startsWith(MASK_PREFIX) ? keys.substring(MASK_PREFIX.length())
+ : keys);
+ return value;
+ }
+
+ @Override
+ public String name() {
+ String keys = keySequenceGenerator.get();
+ return Act.super.name() + "[" + locatorFunction + "][" +
+ (keys.startsWith(MASK_PREFIX) ? MASK_PREFIX
+ : keys) + "]";
+ }
+
+ private static Supplier toSupplier(String keys) {
+ return () -> keys;
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/StoreStorageState.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/StoreStorageState.java
new file mode 100644
index 0000000..053e0ae
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/StoreStorageState.java
@@ -0,0 +1,22 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.microsoft.playwright.BrowserContext;
+import com.microsoft.playwright.Page;
+import jp.co.moneyforward.autotest.framework.action.Act;
+import jp.co.moneyforward.autotest.framework.core.ExecutionEnvironment;
+
+import java.nio.file.Paths;
+
+public class StoreStorageState implements Act {
+ private final String filePath;
+
+ public StoreStorageState(String filePath) {
+ this.filePath = filePath;
+ }
+
+ @Override
+ public Page perform(Page page, ExecutionEnvironment executionEnvironment) {
+ page.context().storageState(new BrowserContext.StorageStateOptions().setPath(Paths.get(filePath)));
+ return page;
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/TableQuery.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/TableQuery.java
new file mode 100644
index 0000000..6af46e8
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/TableQuery.java
@@ -0,0 +1,206 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import com.microsoft.playwright.Locator;
+import com.microsoft.playwright.Page;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.BinaryOperator;
+
+import static com.github.valid8j.classic.Requires.requireNonNull;
+import static com.github.valid8j.fluent.Expectations.require;
+import static com.github.valid8j.fluent.Expectations.value;
+import static java.util.Arrays.asList;
+import static jp.co.moneyforward.autotest.framework.utils.Valid8JCliches.mapToKeyList;
+
+///
+/// A class to query HTML table object as if it were an SQL relation.
+/// Note that this class is designed to select only one column.
+///
+///
+/// ```java
+/// void example() {
+/// try (Playwright playwright = Playwright.create()) {
+/// BrowserType chromium = playwright.chromium();
+/// try (Browser browser = chromium.launch(new BrowserType.LaunchOptions().setHeadless(false))) {
+/// Page page = browser.newPage();
+/// page.navigate(testTableResourcePath());
+/// //#js-ca-main-contents > table > thead
+///
+/// Locator l = TableQuery.select("事業者・年度の切替")
+/// .from("body > table")
+/// .where(term("事業者名", "abc-154206"))
+/// .normalizeWith(normalizerFunction())
+/// .build()
+/// .perform(page)
+/// .getFirst();
+/// }
+/// ```
+///
+///
+/// **Limitation:**
+///
+/// - The target HTML table must have unique headers (`th`) for all columns.
+/// - Only "equal" condition is supported.
+/// - Only conjunctions are supported.
+///
+/// In case you think these need to be improved, contact the development team of *insdog*.
+///
+/// @param tableName A locator string to specify a table within a `Page` object.
+/// @param columnName A column from which value is project to the result.
+/// @param queryTerms Condition terms to select rows in a table.
+/// @param normalizer A `BinaryOperator` to normalize an incomplete row.
+///
+/// @see TableQuery.Term
+///
+public record TableQuery(String tableName, String columnName, List queryTerms,
+ BiFunction, List, List> normalizer) {
+ ///
+ /// A method from which you can start building a query.
+ /// A `Builder` class object, which holds `columnName` as a column to project to the result will be returned.
+ ///
+ /// @param columnName A column to be projected into the result.
+ /// @return A builder object.
+ ///
+ public static TableQuery.Builder select(String columnName) {
+ Builder builder = new Builder();
+ builder.columnName = columnName;
+ return builder;
+ }
+
+ ///
+ /// Performs the query on the given `page`.
+ ///
+ /// @param page A page object on which this query will be performed.
+ /// @return A list of locators, each of whose enclosing row that satisfies the `terms`.
+ ///
+ public List perform(Page page) {
+ String headerLocatorString = String.format("%s thead tr", this.tableName());
+ Locator headerRow = page.locator(headerLocatorString);
+ headerRow.waitFor();
+ Map columnIndices = composeColumnNameIndices(headerRow.locator("th"));
+
+ require(value(columnIndices).function(mapToKeyList())
+ .asList()
+ .toBe()
+ .containing(columnName()));
+
+ List> matches = new ArrayList<>();
+ Optional> lastCompleteRow = Optional.empty();
+ Locator rowLocator = page.locator(String.format("%s > tbody > tr", tableName()));
+ for (int i = 0; i < rowLocator.count(); i++) {
+ Locator eachRowLocator = rowLocator.nth(i);
+ List columnsInEachRow = toColumns(eachRowLocator);
+ if (isCompleteRow(columnsInEachRow, columnIndices))
+ lastCompleteRow = Optional.of(columnsInEachRow);
+ List eachRow = lastCompleteRow.filter(r -> !isCompleteRow(columnsInEachRow, columnIndices))
+ .map(r -> this.normalizer().apply(r, columnsInEachRow))
+ .orElse(columnsInEachRow);
+
+ boolean matched = true;
+ for (Term eachQueryTerm : queryTerms()) {
+ String filterTargetColumnValue = eachRow.get(columnIndices.get(eachQueryTerm.columnName()))
+ .textContent();
+ if (!filterTargetColumnValue.contains(eachQueryTerm.operand())) {
+ matched = false;
+ break;
+ }
+ }
+ if (matched)
+ matches.add(eachRow);
+ }
+ return matches.stream()
+ .map(c -> c.get(columnIndices.get(columnName)))
+ .toList();
+ }
+
+ private static boolean isCompleteRow(List columnsInEachRow, Map columnIndices) {
+ return columnsInEachRow.size() == columnIndices.size();
+ }
+
+ private static Map composeColumnNameIndices(Locator headerCells) {
+ Map columnIndices = new HashMap<>();
+ for (int i = 0; i < headerCells.count(); i++) {
+ String columnName = headerCells.nth(i).textContent();
+ columnIndices.put(columnName, i);
+ }
+ return columnIndices;
+ }
+
+ private static List toColumns(Locator row) {
+ return row.locator("td").all();
+ }
+
+
+ ///
+ /// A class that represents a term in a condition to select rows.
+ /// When a value of the column designated by the `columnName` is equal to `operand`, the term is satisfied.
+ ///
+ /// @param columnName A name of a column.
+ /// @param operand A value
+ ///
+ public record Term(String columnName, String operand) {
+ public static Term term(String columnName, String operand) {
+ return new Term(columnName, operand);
+ }
+ }
+
+ ///
+ /// A builder class for `TableQuery`.
+ ///
+ public static class Builder {
+ private String tableName;
+ private String columnName;
+ private Term[] conditions;
+ private BinaryOperator> normalizer = (lastFullRow, incompleteRow) -> incompleteRow;
+
+ ///
+ /// A table name on which the query will be performed.
+ ///
+ /// @param tableName A locator string of the table.
+ /// @return This object.
+ ///
+ public Builder from(String tableName) {
+ this.tableName = requireNonNull(tableName);
+ return this;
+ }
+
+ ///
+ /// @param terms Conditions with which querying
+ /// @return This object
+ /// @see TableQuery.Term
+ ///
+ public Builder where(Term... terms) {
+ this.conditions = requireNonNull(terms);
+ return this;
+ }
+
+ ///
+ /// @param normalizer A function that normalizes incomplete row.
+ /// @return This object
+ ///
+ public Builder normalizeWith(BinaryOperator> normalizer) {
+ this.normalizer = requireNonNull(normalizer);
+ return this;
+ }
+
+ ///
+ /// Builds a `TableQuery` object from the current field values held by this object.
+ ///
+ /// @return A `TableQuery` object.
+ ///
+ public TableQuery build() {
+ return new TableQuery(this.tableName, columnName, asList(conditions), this.normalizer);
+ }
+
+ ///
+ /// A shorthand method ob `build()`.
+ ///
+ /// @return A built `TableQuery` object.
+ ///
+ public TableQuery $() {
+ return build();
+ }
+ }
+
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/actions/web/Value.java b/src/main/java/jp/co/moneyforward/autotest/actions/web/Value.java
new file mode 100644
index 0000000..8417645
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/actions/web/Value.java
@@ -0,0 +1,11 @@
+package jp.co.moneyforward.autotest.actions.web;
+
+import jp.co.moneyforward.autotest.framework.action.Act;
+import jp.co.moneyforward.autotest.framework.core.ExecutionEnvironment;
+
+public class Value implements Act {
+ @Override
+ public V perform(V value, ExecutionEnvironment executionEnvironment) {
+ return value;
+ }
+}
diff --git a/src/main/java/jp/co/moneyforward/autotest/framework/action/Act.java b/src/main/java/jp/co/moneyforward/autotest/framework/action/Act.java
new file mode 100644
index 0000000..e5b819a
--- /dev/null
+++ b/src/main/java/jp/co/moneyforward/autotest/framework/action/Act.java
@@ -0,0 +1,155 @@
+package jp.co.moneyforward.autotest.framework.action;
+
+import com.github.valid8j.pcond.forms.Printables;
+import jp.co.moneyforward.autotest.framework.core.ExecutionEnvironment;
+import jp.co.moneyforward.autotest.framework.utils.InternalUtils;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+///
+/// This interface represents the smallest and indivisible unit of action in **insdog** 's programming model.
+///
+public interface Act {
+ ///
+ /// Applies this function the given argument: `value`(`T`) and returns the result (`R`).
+ ///
+ /// @param value An argument value.
+ /// @param executionEnvironment An environment in which this function is executed.
+ /// @return the function result.
+ ///
+ R perform(T value, ExecutionEnvironment executionEnvironment);
+
+ ///
+ /// Returns a name of an instance of this interface.
+ ///
+ /// @return A name of this instance.
+ ///
+ default String name() {
+ return InternalUtils.simpleClassNameOf(this.getClass());
+ }
+
+ static Act create(Function func) {
+ return new Func<>(func);
+ }
+
+ static Act create(String name, Function action) {
+ return create(Printables.function(name, action));
+ }
+
+ ///
+ /// A leaf act, which represents a value assignment behavior.
+ ///
+ /// @param The type of the value to be assigned.
+ ///
+ class Let extends Source implements Act