/'
baseUrl: '/ultron/',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'Open-tool', // Usually your GitHub org/user name.
projectName: 'ultron', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
{
docs: {
sidebarPath: './sidebars.ts',
},
theme: {
customCss: './src/css/custom.css',
},
} satisfies Preset.Options,
],
],
themeConfig: {
image: 'img/docusaurus-social-card.jpg',
navbar: {
title: 'Ultron',
logo: {
alt: 'Ultron Logo',
src: 'img/ultron_full_light.png',
},
items: [
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
position: 'left',
label: 'Docs',
},
{
href: 'https://t.me/ultron_framework',
position: 'right',
className: 'header-telegram-link',
'aria-label': 'Telegram',
},
{
href: 'https://github.com/open-tool/ultron',
position: 'right',
className: 'header-github-link',
'aria-label': 'GitHub repository',
},
{
type: 'search',
position: 'right',
},
],
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
algolia: {
appId: 'TLB3E9OO68',
apiKey: '06f26f943a74848657b1e5bec4c85aaf',
indexName: 'open-toolio',
contextualSearch: true,
searchParameters: {},
insights: false,
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
} satisfies Preset.ThemeConfig,
plugins: [
[
'@docusaurus/plugin-client-redirects',
{
fromExtensions: ['html', 'htm'], // /myPage.html -> /myPage
toExtensions: ['exe', 'zip'], // /myAsset -> /myAsset.zip (if latter exists)
redirects: [
{
to: '/docs/',
from: '/',
},
],
},
],
],
};
export default config;
================================================
FILE: docs/package.json
================================================
{
"name": "my-website",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "3.4.0",
"@docusaurus/plugin-client-redirects": "^3.4.0",
"@docusaurus/preset-classic": "3.4.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.4.0",
"@docusaurus/tsconfig": "3.4.0",
"@docusaurus/types": "3.4.0",
"typescript": "~5.2.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 3 chrome version",
"last 3 firefox version",
"last 5 safari version"
]
},
"engines": {
"node": ">=18.0"
}
}
================================================
FILE: docs/sidebars.ts
================================================
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
const sidebars: SidebarsConfig = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
*/
};
export default sidebars;
================================================
FILE: docs/src/components/HomepageFeatures/index.tsx
================================================
import clsx from 'clsx';
import Heading from '@theme/Heading';
import styles from './styles.module.css';
type FeatureItem = {
title: string;
pict: JSX.Element;
description: JSX.Element;
};
const FeatureList: FeatureItem[] = [
{
title: 'Simple',
pict: (
),
description: (
<>
The simplest syntax for UI tests.
hasTestTag("elementId").click()
>
),
},
{
title: 'Stable',
pict: (
),
description: (
<>
No flaky tests
Auto-waits for UI elements
Automatic retries of failed interactions
Custom assertions of executed action
>
),
},
{
title: 'Maintainable',
pict: (
),
description: (
<>
Page Object support
Allure support
Detailed logs
Extendable API
>
),
},
];
function Feature({ title, pict, description }: FeatureItem) {
const imageStyle = `text--center padding-horiz--md ${styles.imageContainer} `
return (
);
}
export default function HomepageFeatures(): JSX.Element {
return (
{FeatureList.map((props, idx) => (
))}
);
}
================================================
FILE: docs/src/components/HomepageFeatures/styles.module.css
================================================
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 150px;
width: 150px;
padding: 10px;
overflow: 'hidden';
display: 'flex';
justify-content: center;
}
.imageContainer {
width: 150px;
height: 150px;
/* display: flex; */
padding: 10px;
margin-left: auto;
margin-right: auto;
/* overflow: hidden; */
}
.imageContainer img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 10px;
}
================================================
FILE: docs/src/css/custom.css
================================================
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
/* :root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
} */
:root {
--ifm-color-primary: #233a60;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #5a85cf;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}
[data-theme='light'] img[src$='#gh-dark-mode-only'],
[data-theme='dark'] img[src$='#gh-light-mode-only'] {
display: none;
}
.header-github-link::before {
content: '';
width: 24px;
height: 24px;
display: flex;
background-color: var(--ifm-navbar-link-color);
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E");
transition: background-color var(--ifm-transition-fast)
var(--ifm-transition-timing-default);
}
.header-github-link:hover::before {
background-color: var(--ifm-navbar-link-hover-color);
}
.header-telegram-link {
display: flex;
align-items: center;
}
.header-telegram-link::before {
content: '';
display: inline-block;
width: 24px;
height: 24px;
background-image: url('/img/telegram-icon.svg');
background-size: contain;
background-repeat: no-repeat;
margin-right: 8px;
}
================================================
FILE: docs/src/pages/index.module.css
================================================
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 4rem 0;
text-align: center;
position: relative;
overflow: hidden;
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
}
================================================
FILE: docs/src/pages/index.tsx
================================================
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
import Heading from '@theme/Heading';
import styles from './index.module.css';
function HomepageHeader() {
const {siteConfig} = useDocusaurusContext();
return (
U L T R O N
{siteConfig.tagline}
GET STARTED
);
}
export default function Home(): JSX.Element {
const {siteConfig} = useDocusaurusContext();
return (
);
}
================================================
FILE: docs/src/pages/markdown-page.md
================================================
---
title: Markdown page example
---
# Markdown page example
You don't need React to write simple standalone pages.
================================================
FILE: docs/static/.nojekyll
================================================
================================================
FILE: docs/tsconfig.json
================================================
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": "."
}
}
================================================
FILE: gradle/libs.versions.toml
================================================
[versions]
agp = "8.9.3"
atomicfu = "0.27.0"
kotlin = "2.1.21"
androidx-activityCompose = "1.10.1"
kotlinxCoroutinesCore = "1.10.2"
kotlinxDatetime = "0.7.1"
compose-plugin = "1.8.2"
okio = "3.11.0"
android-compileSdk = "35"
android-minSdk = "24"
android-targetSdk = "35"
androidx-lifecycle = "2.8.4"
androidx-navigation = "2.7.0-alpha06"
monitor = "1.7.2"
uiTestJunit4Android = "1.8.1"
lifecycleCommonJvm = "2.9.2"
coreKtx = "1.6.1"
[libraries]
androidx-ui-test-junit4-android = { module = "androidx.compose.ui:ui-test-junit4-android", version.ref = "uiTestJunit4Android" }
androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "uiTestJunit4Android" }
atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "atomicfu" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
androidx-monitor = { group = "androidx.test", name = "monitor", version.ref = "monitor" }
androidx-lifecycle-runtime-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
androidx-lifecycle-common-jvm = { group = "androidx.lifecycle", name = "lifecycle-common-jvm", version.ref = "lifecycleCommonJvm" }
androidx-core-ktx = { group = "androidx.test", name = "core-ktx", version.ref = "coreKtx" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
vanniktech-mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.30.0" }
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Tue May 20 16:16:07 MSK 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: gradle.properties
================================================
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
org.gradle.caching=true
org.gradle.configuration-cache=true
kotlin.code.style=official
android.useAndroidX=true
android.nonTransitiveRClass=true
org.jetbrains.compose.experimental.wasm.enabled=true
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.mpp.enableCInteropCommonization=true
kotlin.native.cacheKind=none
GROUP=com.atiurin
POM_ARTIFACT_ID=ultron
VERSION_NAME=2.6.2
================================================
FILE: gradlew
================================================
#!/bin/sh
#
# Copyright 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions $var, ${var}, ${var:-default}, ${var+SET},
# ${var#prefix}, ${var%suffix}, and $( cmd );
# * compound commands having a testable exit status, especially case;
# * various built-in commands including command, set, and ulimit.
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: iosApp/Configuration/Config.xcconfig
================================================
TEAM_ID=
BUNDLE_ID=com.atiurin.samplekmp.sample-kmp
APP_NAME=sample-kmp
================================================
FILE: iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"filename" : "app-icon-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: iosApp/iosApp/Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: iosApp/iosApp/ContentView.swift
================================================
import UIKit
import SwiftUI
import ComposeApp
struct ComposeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
MainViewControllerKt.MainViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
struct ContentView: View {
var body: some View {
ComposeView()
.ignoresSafeArea(.keyboard) // Compose has own keyboard handler
}
}
================================================
FILE: iosApp/iosApp/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSRequiresIPhoneOS
CADisableMinimumFrameDurationOnPhone
UIApplicationSceneManifest
UIApplicationSupportsMultipleScenes
UILaunchScreen
UIRequiredDeviceCapabilities
armv7
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
================================================
FILE: iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: iosApp/iosApp/iOSApp.swift
================================================
import SwiftUI
@main
struct iOSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
================================================
FILE: iosApp/iosApp.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; };
7555FF7B242A565900829871 /* sample-kmp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "sample-kmp.app"; sourceTree = BUILT_PRODUCTS_DIR; };
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
B92378962B6B1156000C7307 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
058557D7273AAEEB004C7B11 /* Preview Content */ = {
isa = PBXGroup;
children = (
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "";
};
42799AB246E5F90AF97AA0EF /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "";
};
7555FF72242A565900829871 = {
isa = PBXGroup;
children = (
AB1DB47929225F7C00F7AF9C /* Configuration */,
7555FF7D242A565900829871 /* iosApp */,
7555FF7C242A565900829871 /* Products */,
42799AB246E5F90AF97AA0EF /* Frameworks */,
);
sourceTree = "";
};
7555FF7C242A565900829871 /* Products */ = {
isa = PBXGroup;
children = (
7555FF7B242A565900829871 /* sample-kmp.app */,
);
name = Products;
sourceTree = "";
};
7555FF7D242A565900829871 /* iosApp */ = {
isa = PBXGroup;
children = (
058557BA273AAA24004C7B11 /* Assets.xcassets */,
7555FF82242A565900829871 /* ContentView.swift */,
7555FF8C242A565B00829871 /* Info.plist */,
2152FB032600AC8F00CF470E /* iOSApp.swift */,
058557D7273AAEEB004C7B11 /* Preview Content */,
);
path = iosApp;
sourceTree = "";
};
AB1DB47929225F7C00F7AF9C /* Configuration */ = {
isa = PBXGroup;
children = (
AB3632DC29227652001CCB65 /* Config.xcconfig */,
);
path = Configuration;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
7555FF7A242A565900829871 /* iosApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
buildPhases = (
F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */,
7555FF77242A565900829871 /* Sources */,
B92378962B6B1156000C7307 /* Frameworks */,
7555FF79242A565900829871 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = iosApp;
packageProductDependencies = (
);
productName = iosApp;
productReference = 7555FF7B242A565900829871 /* sample-kmp.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
7555FF73242A565900829871 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1130;
LastUpgradeCheck = 1540;
ORGANIZATIONNAME = orgName;
TargetAttributes = {
7555FF7A242A565900829871 = {
CreatedOnToolsVersion = 11.3.1;
};
};
};
buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
compatibilityVersion = "Xcode 15.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 7555FF72242A565900829871;
packageReferences = (
);
productRefGroup = 7555FF7C242A565900829871 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
7555FF7A242A565900829871 /* iosApp */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
7555FF79242A565900829871 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Compile Kotlin Framework";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
7555FF77242A565900829871 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
7555FF83242A565900829871 /* ContentView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
7555FFA3242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
7555FFA4242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.3;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
7555FFA6242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
DEVELOPMENT_TEAM = "${TEAM_ID}";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
);
INFOPLIST_FILE = iosApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
ComposeApp,
);
PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
PRODUCT_NAME = "${APP_NAME}";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
7555FFA7242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
DEVELOPMENT_TEAM = "${TEAM_ID}";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
);
INFOPLIST_FILE = iosApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
ComposeApp,
);
PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
PRODUCT_NAME = "${APP_NAME}";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7555FFA3242A565B00829871 /* Debug */,
7555FFA4242A565B00829871 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7555FFA6242A565B00829871 /* Debug */,
7555FFA7242A565B00829871 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 7555FF73242A565900829871 /* Project object */;
}
================================================
FILE: prepare-emulator.bat
================================================
adb shell settings put global development_settings_enabled 1
adb shell settings put global window_animation_scale 0.0
adb shell settings put global transition_animation_scale 0.0
adb shell settings put global animator_duration_scale 0.0
================================================
FILE: prepare-emulator.sh
================================================
adb shell settings put global development_settings_enabled 1
adb shell settings put global window_animation_scale 0.0
adb shell settings put global transition_animation_scale 0.0
adb shell settings put global animator_duration_scale 0.0
================================================
FILE: sample-app/.gitignore
================================================
/build
================================================
FILE: sample-app/build.gradle.kts
================================================
plugins {
id("com.android.application")
id("kotlin-android")
alias(libs.plugins.compose.compiler)
}
android {
namespace = "com.atiurin.sampleapp"
compileSdk = 35
defaultConfig {
applicationId = "com.atiurin.sampleapp"
minSdk = 21
targetSdk = 35
multiDexEnabled = true
testInstrumentationRunner = "com.atiurin.sampleapp.framework.CustomTestRunner"
}
compileOptions {
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
compose = true
buildConfig = true
}
kotlinOptions {
jvmTarget = "17"
}
buildTypes {
debug {
isMinifyEnabled = false
}
}
packagingOptions {
resources.excludes.add("META-INF/DEPENDENCIES")
resources.excludes.add("META-INF/LICENSE")
resources.excludes.add("META-INF/LICENSE.txt")
resources.excludes.add("META-INF/license.txt")
resources.excludes.add("META-INF/NOTICE")
resources.excludes.add("META-INF/NOTICE.txt")
resources.excludes.add("META-INF/notice.txt")
resources.excludes.add("META-INF/AL2.0")
resources.excludes.add("META-INF/LGPL2.1")
resources.excludes.add("META-INF/*.kotlin_module")
}
}
dependencies {
implementation(project(":ultron-compose"))
implementation(project(":ultron-allure"))
implementation(project(":ultron-android"))
implementation(Libs.kotlinStdlib)
implementation(Libs.coroutines)
implementation(Libs.appcompat)
implementation(Libs.androidXKtx)
implementation(Libs.supportV4)
implementation(Libs.material)
implementation(Libs.material3)
implementation(Libs.constraintLayout)
implementation(Libs.recyclerView)
implementation(Libs.cardview)
implementation(Libs.espressoIdlingResource)
implementation(libs.androidx.navigation.compose)
//compose
implementation(Libs.composeUi)
implementation(Libs.composeUiTooling)
implementation(Libs.composeFoundation)
implementation(Libs.composeMaterial)
implementation(Libs.composeMaterialIconsCore)
implementation(Libs.composeMaterialIconsExtend)
implementation(Libs.activityCompose)
// AndroidJUnitRunner and JUnit Rules
testImplementation(Libs.junit)
testImplementation(Libs.robolectric)
testImplementation(Libs.mockito)
testImplementation(Libs.androidXTextCore)
androidTestImplementation(Libs.androidXRules)
androidTestImplementation(Libs.androidXTruth)
androidTestImplementation(Libs.androidXJunit)
// Espresso dependencies
androidTestImplementation(Libs.espressoIntents)
androidTestImplementation(Libs.espressoAccessibility)
androidTestImplementation(Libs.espressoConcurrent)
}
================================================
FILE: sample-app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/CustomTestRunner.kt
================================================
package com.atiurin.sampleapp.framework
import com.atiurin.ultron.allure.UltronAllureTestRunner
class CustomTestRunner : UltronAllureTestRunner() {}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/DummyMetaObject.kt
================================================
package com.atiurin.sampleapp.framework
data class DummyMetaObject(val value: String)
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/Log.kt
================================================
package com.atiurin.sampleapp.framework
import android.os.SystemClock
import android.util.Log
object Log {
const val LOG_TAG = "Ultron"
fun info(message: String) = Log.i(LOG_TAG, message)
fun debug(message: String) = Log.d(LOG_TAG, message)
fun error(message: String, name: String) = Log.e(LOG_TAG, message)
fun warn(message: String) = Log.w(LOG_TAG, message)
fun time(desc: String, block: () -> R) : R{
val startTime = SystemClock.elapsedRealtime()
val result = block()
debug("$desc duration ${SystemClock.elapsedRealtime() - startTime} ms")
return result
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/ScreenshotLifecycleListener.kt
================================================
package com.atiurin.sampleapp.framework
import com.atiurin.ultron.core.common.Operation
import com.atiurin.ultron.core.common.OperationResult
import com.atiurin.ultron.listeners.UltronLifecycleListener
class ScreenshotLifecycleListener : UltronLifecycleListener(){
override fun before(operation: Operation) {
}
override fun after(operationResult: OperationResult) {
operationResult.operation
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/ultronext/UltronComposeExt.kt
================================================
package com.atiurin.sampleapp.framework.ultronext
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.SemanticsMatcher
import com.atiurin.ultron.core.compose.nodeinteraction.UltronComposeSemanticsNodeInteraction
import com.atiurin.ultron.core.compose.operation.UltronComposeOperationParams
import com.atiurin.ultron.extensions.assertMatches
// ------ custom action definition ------
// step 1: extend base class [UltronComposeSemanticsNodeInteraction] with custom action logic
fun UltronComposeSemanticsNodeInteraction.getProgress() = execute(
UltronComposeOperationParams(
operationName = "Get '${elementInfo.name}' current progress",
operationDescription = "Compose get current progress of '${elementInfo.name}' during $timeoutMs ms"
)
) {
getNodeConfigProperty(SemanticsProperties.ProgressBarRangeInfo).current
}
// step 2: extend [SemanticsMatcher] class with new action method
fun SemanticsMatcher.getProgress(): Float = UltronComposeSemanticsNodeInteraction(this).getProgress()
// ------ custom assertion definition ------
// step 1: define custom matcher logic - use function, not a subclass (it's a compose way)
fun hasProgress(expected: Float) = SemanticsMatcher(
description = "ProgressBarRangeInfo.current = [$expected]"
) { node ->
val current = node.config[SemanticsProperties.ProgressBarRangeInfo].current
current == expected
}
// step 2: extend [UltronComposeSemanticsNodeInteraction] class with extension function and [assertMatches] for easier validation
//fun UltronComposeSemanticsNodeInteraction.assertProgress(expected: Float) = assertMatches(hasProgress(expected))
// step 3: extend [SemanticsMatcher] class with new assertion method
//fun SemanticsMatcher.assertProgress(expected: Float) = UltronComposeSemanticsNodeInteraction(this).assertProgress(expected)
// ------ custom ui element definition ------
// just make a subclass of [UltronComposeSemanticsNodeInteraction] and add new method
open class ProgressBar(val matcher: SemanticsMatcher) : UltronComposeSemanticsNodeInteraction(matcher) {
fun assertProgress(expected: Float) = matcher.assertMatches(hasProgress(expected))
}
fun progressBar(block: () -> SemanticsMatcher): Lazy = lazy { ProgressBar(block()) }
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/ultronext/UltronEspressoExt.kt
================================================
package com.atiurin.sampleapp.framework.ultronext
import android.view.View
import android.widget.CheckBox
import android.widget.TextView
import androidx.test.espresso.DataInteraction
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import com.atiurin.ultron.core.espresso.UltronEspressoInteraction
import com.atiurin.ultron.core.espresso.action.UltronEspressoActionParams
import com.atiurin.ultron.core.espresso.recyclerview.UltronRecyclerView
import com.atiurin.ultron.core.espresso.recyclerview.UltronRecyclerViewItem
import org.hamcrest.Matcher
fun UltronEspressoInteraction.appendText(value: String) = perform(
params = UltronEspressoActionParams(
operationName = "Append text '$value' to ${getInteractionMatcher()}",
operationDescription = "Awesome description"
)
) { _, view ->
val textView = (view as TextView)
textView.text = "${textView.text}$value"
}
//support action for all Matcher
fun Matcher.appendText(value: String) = UltronEspressoInteraction(onView(this)).appendText(value)
//support action for all ViewInteractions
fun ViewInteraction.appendText(text: String) = UltronEspressoInteraction(this).appendText(text)
//support action for all DataInteractions
fun DataInteraction.appendText(text: String) = UltronEspressoInteraction(this).appendText(text)
//support action for RecyclerView list. appendText action is useless for RecyclerView.
// This is just an example of how to add new behaviour for UltronRecyclerView
fun UltronRecyclerView.appendText(text: String) = apply { recyclerViewMatcher.appendText(text) }
//support action for RecyclerView item
fun UltronRecyclerViewItem.appendText(text: String) = apply { getMatcher().appendText(text) }
// assertion example
fun UltronEspressoInteraction.assertChecked(expectedState: Boolean) = assertMatches { view ->
(view as CheckBox).isChecked == expectedState
}
fun Matcher.assertChecked(expectedState: Boolean) = UltronEspressoInteraction(onView(this)).assertChecked(expectedState)
fun ViewInteraction.assertChecked(expectedState: Boolean) = UltronEspressoInteraction(this).assertChecked(expectedState)
fun DataInteraction.assertChecked(expectedState: Boolean) = UltronEspressoInteraction(this).assertChecked(expectedState)
fun UltronEspressoInteraction.getViewSimple(): View = execute { _, view ->
view
}
fun Matcher.getViewSimple() = UltronEspressoInteraction(onView(this)).getViewSimple()
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/ultronext/UltronEspressoWebExt.kt
================================================
package com.atiurin.sampleapp.framework.ultronext
import androidx.test.espresso.web.webdriver.DriverAtoms
import com.atiurin.ultron.core.espressoweb.webelement.UltronWebElement
// add action on wenView
fun UltronWebElement.appendText(text: String) = apply {
executeOperation(
getUltronWebActionOperation(
webInteractionBlock = {
webInteractionBlock().perform(DriverAtoms.webKeys(text))
},
name = "${elementInfo.name} appendText '$text'",
description = "${elementInfo.name} appendText '$text' during $timeoutMs ms"
)
)
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/ultronext/UltronUiAutomatorExt.kt
================================================
package com.atiurin.sampleapp.framework.ultronext
import com.atiurin.ultron.core.common.UltronOperationType
import com.atiurin.ultron.core.uiautomator.UiAutomatorActionType
import com.atiurin.ultron.core.uiautomator.uiobject2.UltronUiObject2
//actually, UltronUiObject2 already has the same method addText
// this is just an example of how to extend UltronUiObject2
fun UltronUiObject2.appendText(appendText: String) = apply {
executeAction(
actionBlock = { uiObject2ProviderBlock()!!.text += appendText },
name = "AppendText '$appendText' to ${elementInfo.name}",
description = "UiObject2 action '${UiAutomatorActionType.ADD_TEXT}' ${elementInfo.name} appendText '$appendText' during $timeoutMs ms"
)
}
enum class CustomUltronOperations : UltronOperationType {
ADD_TEXT_PREFIX, ASSERT_HAS_ANY_CHILD
}
// add extension function to UltronUiObject2 that calls `executeAssertion`
fun UltronUiObject2.assertHasAnyChild() = apply {
executeAssertion(
assertionBlock = { uiObject2ProviderBlock()!!.childCount > 0 },
name = "Assert ${elementInfo.name} has any child",
type = CustomUltronOperations.ASSERT_HAS_ANY_CHILD,
description = "UiObject2 assertion '${CustomUltronOperations.ASSERT_HAS_ANY_CHILD}' of ${elementInfo.name} during $timeoutMs ms"
)
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/utils/AssertUtils.kt
================================================
package com.atiurin.sampleapp.framework.utils
import org.junit.Assert
object AssertUtils {
fun assertException(expected: Boolean = true, block: () -> Unit) {
var exceptionOccurs = false
try {
block()
} catch (ex: Throwable) {
// throw ex
exceptionOccurs = true
}
Assert.assertEquals(expected, exceptionOccurs)
}
fun assertExecTimeMoreThen(time: Long, block: () -> Unit) {
val startTime = System.currentTimeMillis()
try {
block()
} catch (ex: Throwable) {
}
Assert.assertTrue(System.currentTimeMillis() - startTime >= time)
}
fun assertExecTimeLessThen(time: Long, block: () -> Unit) {
val startTime = System.currentTimeMillis()
try {
block()
} catch (ex: Throwable) {
}
Assert.assertTrue(System.currentTimeMillis() - startTime <= time)
}
fun assertExecTimeBetween(minTime: Long, maxTime: Long, block: () -> Unit) {
val startTime = System.currentTimeMillis()
try {
block()
} catch (ex: Throwable) {
}
val execTime = System.currentTimeMillis() - startTime
Assert.assertTrue("ExecTime in $minTime .. $maxTime, but actual $execTime", execTime in minTime..maxTime)
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/utils/EspressoUtil.kt
================================================
package com.atiurin.sampleapp.framework.utils
import androidx.test.espresso.Espresso
import androidx.test.platform.app.InstrumentationRegistry
object EspressoUtil {
// fun openOptionsMenu() = apply {
// Espresso.openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getInstrumentation().context)
// }
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/utils/TestDataUtils.kt
================================================
package com.atiurin.sampleapp.framework.utils
import androidx.test.platform.app.InstrumentationRegistry
object TestDataUtils {
fun getResourceString(resourceId: Int): String {
return InstrumentationRegistry.getInstrumentation().targetContext.resources.getString(resourceId)
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/utils/TimeUtils.kt
================================================
package com.atiurin.sampleapp.framework.utils
import android.annotation.SuppressLint
import java.time.Clock
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
object TimeUtils {
@SuppressLint("NewApi")
fun formatTimestamp(timestamp: Long): String {
val instant = Instant.ofEpochMilli(timestamp)
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.withZone(ZoneId.systemDefault())
return formatter.format(instant)
}
fun getTimestampStartOfDay(): Long {
return LocalDate.now(Clock.systemUTC())
.atStartOfDay()
.toInstant(ZoneOffset.UTC)
.toEpochMilli()
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/ChatPage.kt
================================================
package com.atiurin.sampleapp.pages
import android.view.View
import androidx.test.espresso.matcher.ViewMatchers.*
import com.atiurin.sampleapp.R
import com.atiurin.ultron.allure.step.step
import com.atiurin.ultron.page.Page
import com.atiurin.ultron.core.espresso.recyclerview.UltronRecyclerViewItem
import com.atiurin.ultron.core.espresso.recyclerview.withRecyclerView
import com.atiurin.ultron.core.espresso.UltronEspresso
import com.atiurin.ultron.custom.espresso.matcher.withSuitableRoot
import com.atiurin.ultron.extensions.*
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
object ChatPage : Page() {
fun assertPageDisplayed() = apply {
step("Assert friends list page displayed") {
messagesList.isDisplayed()
}
}
val messagesList = withRecyclerView(R.id.messages_list).withName("Messages list")
val clearHistoryBtn = withText("Clear history")
val inputMessageText = withId(R.id.message_input_text)
val sendMessageBtn = withId(R.id.send_button)
val toolbarTitle = withId(R.id.toolbar_title)
fun getMessageListItem(text: String): ChatRecyclerItem {
return messagesList.getItem(hasDescendant(
allOf(
withId(R.id.message_text),
withText(text)
)
)
)
}
private fun getListItemAtPosition(position: Int): ChatRecyclerItem {
return messagesList.getItem(position)
}
fun getTitle(title: String): Matcher {
return allOf(withId(R.id.toolbar_title), withText(title))
}
class ChatRecyclerItem : UltronRecyclerViewItem(){
val text by lazy { getChild(withId(R.id.message_text)) }
}
fun sendMessage(text: String) = apply {
step("Send message with text '$text") {
inputMessageText.typeText(text)
sendMessageBtn.click()
this.getMessageListItem(text).text
.isDisplayed()
.hasText(text)
}
}
fun assertMessageTextAtPosition(position: Int, text: String) = apply {
step("Assert item at position $position has text '$text'") {
this.getListItemAtPosition(position).text.isDisplayed().hasText(text)
}
}
fun assertToolbarTitle(text: String){
toolbarTitle.withTimeout(1000).withName("Toolbar title").hasText(text)
}
fun assertToolbarTitleWithSuitableRoot(text: String){
toolbarTitle.withSuitableRoot().withName("Toolbar title").hasText(text)
}
fun clearHistory() = apply {
step("Clear chat history") {
UltronEspresso.openContextualActionModeOverflowMenu()
clearHistoryBtn.click()
messagesList.assertEmpty()
}
}
fun assertMessageDisplayed(text: String) {
getMessageListItem(text).text
.isDisplayed()
.hasText(text)
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/ComposeElementsPage.kt
================================================
package com.atiurin.sampleapp.pages
import androidx.compose.ui.test.hasTestTag
import com.atiurin.sampleapp.activity.ComposeElementsActivity
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.clickListenerButton
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.likesCounterButton
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.radioButtonFemaleTestTag
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.radioButtonMaleTestTag
import com.atiurin.sampleapp.compose.RegionsClickListenerTestTags
import com.atiurin.sampleapp.framework.ultronext.progressBar
import com.atiurin.ultron.page.Page
object ComposeElementsPage : Page() {
val status = hasTestTag(ComposeElementsActivity.Constants.statusText)
val likesCounter = hasTestTag(likesCounterButton)
val longAndDoubleClickButton = hasTestTag(clickListenerButton)
val regionsNode = hasTestTag(RegionsClickListenerTestTags.regionsNode)
val clickedRegion = hasTestTag(RegionsClickListenerTestTags.regionsClickedText)
val editableText = hasTestTag(ComposeElementsActivity.editableText)
val swipeableNode = hasTestTag(ComposeElementsActivity.swipeableNode)
val disabledButton = hasTestTag(ComposeElementsActivity.disabledButton)
val simpleCheckbox = hasTestTag(ComposeElementsActivity.simpleCheckbox)
val progressBar by progressBar { hasTestTag(ComposeElementsActivity.progressBar) }
val maleRadioButton = hasTestTag(radioButtonMaleTestTag)
val femaleRadioButton = hasTestTag(radioButtonFemaleTestTag)
val notExistedElement = hasTestTag("NotExistedTestTag")
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/ComposeListPage.kt
================================================
package com.atiurin.sampleapp.pages
import androidx.compose.ui.test.hasAnyDescendant
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import com.atiurin.sampleapp.compose.ListItemPositionPropertyKey
import com.atiurin.sampleapp.compose.contactNameTestTag
import com.atiurin.sampleapp.compose.contactStatusTestTag
import com.atiurin.sampleapp.compose.contactsListContentDesc
import com.atiurin.sampleapp.compose.getContactItemTestTagById
import com.atiurin.sampleapp.data.entities.Contact
import com.atiurin.ultron.core.compose.list.UltronComposeListItem
import com.atiurin.ultron.core.compose.list.composeList
import com.atiurin.ultron.extensions.withDescription
import com.atiurin.ultron.page.Page
object ComposeListPage : Page() {
val lazyList = composeList(
listMatcher = hasContentDescription(contactsListContentDesc),
positionPropertyKey = ListItemPositionPropertyKey
).withDescription("Contacts list")
fun assertContactStatus(contact: Contact) = apply {
getContactItemByTestTag(contact).status.assertTextEquals(contact.status)
}
fun getItemByPosition(position: Int): ComposeFriendListItem {
return lazyList.getItem(position)
}
fun getFirstVisibleItem(): ComposeFriendListItem = lazyList.getFirstVisibleItem()
fun getItemByIndex(index: Int): ComposeFriendListItem = lazyList.getVisibleItem(index)
fun getContactItemByTestTag(contact: Contact): ComposeFriendListItem = lazyList.getItem(hasTestTag(getContactItemTestTagById(contact)))
fun getContactItemByName(contact: Contact): ComposeFriendListItem = lazyList
.getItem(hasAnyDescendant(hasText(contact.name) and hasTestTag(contactNameTestTag)).withDescription("Contact '${contact.name}'"))
class ComposeFriendListItem : UltronComposeListItem() {
val name by child { hasTestTag(contactNameTestTag).withDescription("Contact name") }
val status by lazy { getChild(hasTestTag(contactStatusTestTag).withDescription("Contact status")) }
val notExisted by child { hasTestTag("NotExistedChild").withDescription("Not existed child") }
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/ComposeSecondPage.kt
================================================
package com.atiurin.sampleapp.pages
import androidx.compose.ui.test.hasTestTag
import com.atiurin.ultron.page.Page
object ComposeSecondPage : Page() {
val name = hasTestTag("name")
val status = hasTestTag("status")
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/FriendsListPage.kt
================================================
package com.atiurin.sampleapp.pages
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.atiurin.sampleapp.R
import com.atiurin.sampleapp.data.entities.Contact
import com.atiurin.ultron.allure.step.step
import com.atiurin.ultron.core.espresso.recyclerview.UltronRecyclerViewImpl
import com.atiurin.ultron.core.espresso.recyclerview.UltronRecyclerViewItem
import com.atiurin.ultron.core.espresso.recyclerview.withRecyclerView
import com.atiurin.ultron.custom.espresso.matcher.withSuitableRoot
import com.atiurin.ultron.extensions.isDisplayed
import com.atiurin.ultron.extensions.withName
import com.atiurin.ultron.page.Page
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.containsString
import org.junit.Assert
object FriendsListPage : Page() {
val recycler = withRecyclerView(R.id.recycler_friends,
implementation = UltronRecyclerViewImpl.PERFORMANCE).withName("Friends list")
fun assertPageDisplayed() = apply {
step("Assert friends list page displayed") {
recycler.recyclerViewMatcher.isDisplayed()
}
}
fun assertPageDisplayedWithSuitableRoot() = apply {
step("Assert friends list page displayed with suitable root") {
recycler.withSuitableRoot().isDisplayed()
}
}
class FriendRecyclerItem : UltronRecyclerViewItem() {
val name by child(withId(R.id.tv_name).withName("Friend name"))
val status by lazy { getChild(withId(R.id.tv_status)).withName("Status") }
val avatar by lazy { getChild(withId(R.id.avatar)).withName("Avatar") }
}
fun getListItem(contactName: String): FriendRecyclerItem {
return recycler.getItem(
hasDescendant(allOf(withId(R.id.tv_name), withText(contactName))).withName("Friend '$contactName")
)
}
fun getListItem(positions: Int): FriendRecyclerItem {
return recycler.getItem(positions)
}
fun openChat(name: String) = apply {
step("Open chat with friend '$name'") {
this.getListItem(name).click()
ChatPage { assertPageDisplayed() }
}
}
fun assertStatus(name: String, status: String) = apply {
step("Assert friend with name '$name' has status '$status'") {
getListItem(name).status.hasText(status).isDisplayed()
}
}
fun assertName(nameText: String) = apply {
step("Assert friend name '$nameText' in the right place") {
getListItem(nameText).name.hasText(nameText).isDisplayed()
}
}
fun assertFriendsListSize(size: Int) {
Assert.assertEquals(size, recycler.getSize())
}
fun getItemMatcher(contact: Contact) = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(contact.name))))
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/UiElementsPage.kt
================================================
package com.atiurin.sampleapp.pages
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.atiurin.sampleapp.R
import com.atiurin.ultron.page.Page
object UiElementsPage : Page() {
val notExistElement = withText("Some not existed text element")
val button = withId(R.id.button1)
val eventStatus = withId(R.id.last_event_status)
val radioVisibleButton = withId(R.id.radio_visible)
val radioInvisibleButton = withId(R.id.radio_invisible)
val radioGoneButton = withId(R.id.radio_gone)
val checkBoxClickable = withId(R.id.checkbox_clickable)
val checkBoxEnabled = withId(R.id.checkbox_enable)
val checkBoxSelected = withId(R.id.checkbox_selected)
val checkBoxFocusable = withId(R.id.checkbox_focusable)
val checkBoxJsEnabled = withId(R.id.checkbox_js_enabled)
val editTextContentDesc = withId(R.id.et_contentDesc)
val webView = withId(R.id.webview)
val appCompatTextView = withId(R.id.app_compat_text)
val imageView = withId(R.id.swipe_image_view)
val imageView2 = withId(R.id.image_view2)
val emptyNotClickableImageView = withId(R.id.empty_image_view)
val dialogButtonOk = onView(withText("OK")).inRoot(isDialog())
val popupButtonCancel = onView(withText("Cancel")).inRoot(isPlatformPopup())
val hiddenButton = withId(R.id.exist_hidden_button)
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/UiObject2ElementsPage.kt
================================================
package com.atiurin.sampleapp.pages
import androidx.test.uiautomator.By
import com.atiurin.sampleapp.R
import com.atiurin.ultron.core.uiautomator.uiobject2.UltronUiObject2.Companion.by
import com.atiurin.ultron.core.uiautomator.uiobject2.UltronUiObject2.Companion.byResId
class UiObject2ElementsPage {
val notExistedObject = by(By.res("com.atiurin.sampleapp","123123123123"))
val button = byResId(R.id.button1)
val eventStatus = byResId(R.id.last_event_status)
val radioGroup = byResId(R.id.radio_group_visibility)
val radioVisibleButton = byResId(R.id.radio_visible)
val radioInvisibleButton = byResId(R.id.radio_invisible)
val radioGoneButton = byResId(R.id.radio_gone)
val checkBoxClickable = byResId(R.id.checkbox_clickable)
val checkBoxEnabled = byResId(R.id.checkbox_enable)
val checkBoxSelected = byResId(R.id.checkbox_selected)
val checkBoxFocusable = byResId(R.id.checkbox_focusable)
val checkBoxJsEnabled = byResId(R.id.checkbox_js_enabled)
val editTextContentDesc = byResId(R.id.et_contentDesc)
val textElement = by(By.text("some text"))
val contentDescElement = by(By.desc("Content desc"))
val webView = byResId(R.id.webview)
val appCompatTextView = byResId(R.id.app_compat_text)
val swipableImageView = byResId(R.id.swipe_image_view)
val emptyImageView = byResId(R.id.empty_image_view)
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/UiObject2FriendsListPage.kt
================================================
package com.atiurin.sampleapp.pages
import androidx.test.uiautomator.By
import com.atiurin.sampleapp.R
import com.atiurin.sampleapp.data.repositories.ContactRepositoty
import com.atiurin.ultron.core.uiautomator.uiobject2.UltronUiObject2.Companion.by
import com.atiurin.ultron.core.uiautomator.uiobject2.UltronUiObject2.Companion.byResId
import com.atiurin.ultron.page.Page
object UiObject2FriendsListPage : Page() {
val list = byResId(R.id.recycler_friends)
val topElement = by(By.text(ContactRepositoty.getFirst().name))
val bottomElement = by(By.text(ContactRepositoty.getLast().name))
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/UiObjectElementsPage.kt
================================================
package com.atiurin.sampleapp.pages
import com.atiurin.sampleapp.R
import com.atiurin.ultron.core.uiautomator.uiobject.UltronUiObject.Companion.uiResId
class UiObjectElementsPage {
val notExistedObject = uiResId(R.id.send_button)
val button = uiResId(R.id.button1)
val eventStatus = uiResId(R.id.last_event_status)
val radioGroup = uiResId(R.id.radio_group_visibility)
val radioVisibleButton = uiResId(R.id.radio_visible)
val radioInvisibleButton = uiResId(R.id.radio_invisible)
val radioGoneButton = uiResId(R.id.radio_gone)
val checkBoxClickable = uiResId(R.id.checkbox_clickable)
val checkBoxEnabled = uiResId(R.id.checkbox_enable)
val checkBoxSelected = uiResId(R.id.checkbox_selected)
val checkBoxFocusable = uiResId(R.id.checkbox_focusable)
val checkBoxJsEnabled = uiResId(R.id.checkbox_js_enabled)
val editTextContentDesc = uiResId(R.id.et_contentDesc)
val webView = uiResId(R.id.webview)
val appCompatTextView = uiResId(R.id.app_compat_text)
val swipableImageView = uiResId(R.id.swipe_image_view)
val emptyImageView = uiResId(R.id.empty_image_view)
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/WebViewPage.kt
================================================
package com.atiurin.sampleapp.pages
import com.atiurin.ultron.core.espressoweb.webelement.UltronWebElements.Companion.classNames
import com.atiurin.ultron.core.espressoweb.webelement.UltronWebElement.Companion.className
import com.atiurin.ultron.core.espressoweb.webelement.UltronWebElement.Companion.id
import com.atiurin.ultron.page.Page
class WebViewPage : Page() {
companion object{
const val BUTTON2_TITLE = "button2 clicked"
const val APPLE_LINK_TEXT = "Apple"
const val APPLE_LINK_HREF = "fake_link.html"
}
val textInput = id("text_input")
val buttonUpdTitle = id("button1")
val buttonSetTitle2 = id("button2")
val buttonSetTitleActive = id("button3")
val title = id("title")
val titleWithCss = className("css_title")
val appleLink = id("apple_link")
val buttons = classNames("button")
val notExistedElement = id("Not existed element")
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/uiblock/ComposeUiBlockScreen.kt
================================================
package com.atiurin.sampleapp.pages.uiblock
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.hasTestTag
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.contactBlock1Tag
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.contactBlock2Tag
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.contactNameTag
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.contactStatusTag
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.contactsListTag
import com.atiurin.sampleapp.pages.uiblock.ComposeListUiBlock.Companion.listBlockDesc
import com.atiurin.ultron.core.compose.child
import com.atiurin.ultron.core.compose.page.UltronComposeUiBlock
import com.atiurin.ultron.extensions.withName
import com.atiurin.ultron.page.Screen
object ComposeUiBlockScreen : Screen(){
val contactBlock1 = ContactUiBlockWithoutDesc(hasTestTag(contactBlock1Tag))
val contactBlock2 = ContactUiBlockWithDesc(hasTestTag(contactBlock2Tag), "Block parent")
val contactListBlock = ComposeListUiBlock(hasTestTag(contactsListTag), listBlockDesc)
}
class ContactUiBlockWithoutDesc(blockMatcher: SemanticsMatcher) : UltronComposeUiBlock(blockMatcher) {
val nameWithoutDeepSearch = child(hasTestTag(contactNameTag), descendantSearch = false).withName("No deep search element")
val statusDeepSearchText = child(hasTestTag(contactStatusTag))
}
class ContactUiBlockWithDesc(blockMatcher: SemanticsMatcher, blockDescription: String) : UltronComposeUiBlock(blockMatcher, blockDescription) {
val name = child(hasTestTag(contactNameTag)).withName("$сhildNameDesc $blockDescription")
val status = child(hasTestTag(contactStatusTag))
companion object {
const val сhildNameDesc = "NamE"
}
}
class ComposeListUiBlock(parent: SemanticsMatcher, blockDescription: String) : UltronComposeUiBlock(parent) {
val itemWithoutDesc = child(uiBlock = ContactUiBlockWithoutDesc(hasTestTag(contactBlock1Tag)))
val item1BlockWithDesc = child(ContactUiBlockWithDesc(hasTestTag(contactBlock1Tag), "1 $descriptionPrefix $blockDescription"))
val item2BlockFactory = child(
childMatcher = hasTestTag(contactBlock2Tag),
uiBlockFactory = { updatedMatcher ->
ContactUiBlockWithDesc(updatedMatcher, blockDescription = "2 $descriptionPrefix $blockDescription")
}
)
companion object {
const val descriptionPrefix = "Item with parent"
val listBlockDesc = "ListBlock"
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/uiblock/EspressoUiBlockScreen.kt
================================================
package com.atiurin.sampleapp.pages.uiblock
import android.view.View
import androidx.test.espresso.matcher.ViewMatchers.withId
import com.atiurin.sampleapp.R
import com.atiurin.ultron.core.espresso.UltronEspressoUiBlock
import com.atiurin.ultron.extensions.withName
import com.atiurin.ultron.page.Screen
import org.hamcrest.Matcher
object EspressoUiBlockScreen : Screen() {
val contactItem1 = ContactItemUiBlock(withId(R.id.contact_item_1), "Item 1")
val blockWithoutDeepSearch = ContactItemUiBlockWithoutDeepSearch(withId(R.id.contact_item_2))
val contactsListBlock = ContactsListUiBlock(withId(R.id.contact_items), "Items list")
}
class ContactItemUiBlock(blockMatcher: Matcher, blockDescription: String) : UltronEspressoUiBlock(blockMatcher) {
val name = child(withId(R.id.name)).withName("Contact name with parent $blockDescription")
val status = child(withId(R.id.status)).withName("Contact item status")
val deepSearchChild = child(withId(R.id.deep_search_child))
}
class ContactItemUiBlockWithoutDeepSearch(parent: Matcher) : UltronEspressoUiBlock(parent) {
val deepSearchFalse = child(withId(R.id.deep_search_child), descendantSearch = false)
}
class ContactsListUiBlock(blockMatcher: Matcher, blockDescription: String) : UltronEspressoUiBlock(blockMatcher, blockDescription) {
val item1 = child(ContactItemUiBlock(withId(R.id.contact_item_1), "Item 1"))
val item2 = child(
childMatcher = withId(R.id.contact_item_2),
uiBlockFactory = { updatedMatcher ->
ContactItemUiBlock(updatedMatcher, blockDescription = "Contact Item 2 with parent $blockDescription")
}
)
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/uiblock/UiObject2UiBlockScreen.kt
================================================
package com.atiurin.sampleapp.pages.uiblock
import androidx.test.uiautomator.BySelector
import com.atiurin.sampleapp.R
import com.atiurin.ultron.core.uiautomator.uiobject2.UltronUiObject2.Companion.bySelector
import com.atiurin.ultron.core.uiautomator.uiobject2.UltronUiObject2UiBlock
import com.atiurin.ultron.page.Screen
object UiObject2UiBlockScreen : Screen() {
val block1 = ContactItemUiObject2Block { bySelector(R.id.contact_item_1) }
val block2 = ContactItemUiObject2Block("Block 2") { bySelector(R.id.contact_item_2) }
val blocks = UiObject2ListUiBlock("Item blocks") { bySelector(R.id.contact_items) }
}
class ContactItemUiObject2Block(blockDesc: String = "", blockSelector: () -> BySelector) : UltronUiObject2UiBlock(blockDesc, blockSelector) {
val name = child(bySelector(R.id.name)).withName("Name in block $blockDesc")
val status = child(bySelector(R.id.status))
val deepSearchChild = child(bySelector(R.id.deep_search_child))
val notExisted = child(bySelector(R.id.recycler_friends)).withTimeout(100)
}
class UiObject2ListUiBlock(desc: String = "", parent: () -> BySelector) : UltronUiObject2UiBlock(desc, parent) {
val item1 = child(ContactItemUiObject2Block { bySelector(R.id.contact_item_1) })
val item2 = child(
selector = bySelector(R.id.contact_item_2),
description = "Item 2 in block $desc",
uiBlockFactory = { desc, selector ->
ContactItemUiObject2Block(desc, selector)
}
)
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/pages/uiblock/WebElementUiBlockScreen.kt
================================================
package com.atiurin.sampleapp.pages.uiblock
import com.atiurin.ultron.core.espressoweb.webelement.UltronWebElement
import com.atiurin.ultron.core.espressoweb.webelement.UltronWebElement.Companion.className
import com.atiurin.ultron.core.espressoweb.webelement.UltronWebElement.Companion.id
import com.atiurin.ultron.core.espressoweb.webelement.UltronWebElementUiBlock
import com.atiurin.ultron.page.Screen
object WebElementUiBlockScreen : Screen() {
val teacherBlock = WebBlock(id("teacher"), "Teacher block")
val studentWithoutDesc = WebBlockWithoutDesc(id("student"))
val persons = WebPersonsBlock(id("persons"), "persons block")
}
class WebBlock(blockElement: UltronWebElement, blockDescription: String): UltronWebElementUiBlock(blockElement, blockDescription){
val name = child(className("person_name")).withName("Name of $blockDescription")
}
class WebBlockWithoutDesc(blockElement: UltronWebElement): UltronWebElementUiBlock(blockElement){
val name = child(className("person_name"))
}
class WebPersonsBlock(blockElement: UltronWebElement, blockDescription: String): UltronWebElementUiBlock(blockElement, blockDescription){
val teacher = child(WebBlock(id("teacher"), "Teacher child of $blockDescription"))
val studentWithoutDesc = child(WebBlockWithoutDesc(id("student")))
val student = child(id("student")){ modifiedElement ->
WebBlock(modifiedElement, "student child of $blockDescription")
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/BaseTest.kt
================================================
package com.atiurin.sampleapp.tests
import android.os.Environment
import androidx.test.platform.app.InstrumentationRegistry
import com.atiurin.sampleapp.data.repositories.CURRENT_USER
import com.atiurin.sampleapp.managers.AccountManager
import com.atiurin.ultron.allure.config.UltronAllureConfig
import com.atiurin.ultron.core.compose.config.UltronComposeConfig
import com.atiurin.ultron.core.compose.listeners.ComposDebugListener
import com.atiurin.ultron.core.config.UltronCommonConfig
import com.atiurin.ultron.core.config.UltronConfig
import com.atiurin.ultron.core.espresso.recyclerview.UltronRecyclerViewImpl
import com.atiurin.ultron.core.test.UltronTest
import com.atiurin.ultron.testlifecycle.rulesequence.RuleSequence
import com.atiurin.ultron.testlifecycle.setupteardown.SetUpRule
import org.junit.BeforeClass
import org.junit.Rule
abstract class BaseTest : UltronTest(){
val setupRule = SetUpRule("Login user rule")
.add(name = "Login valid user $CURRENT_USER") {
AccountManager(InstrumentationRegistry.getInstrumentation().targetContext).login(
CURRENT_USER.login, CURRENT_USER.password
)
}
@get:Rule
open val ruleSequence = RuleSequence(setupRule)
companion object {
@BeforeClass
@JvmStatic
fun config() {
UltronConfig.Espresso.RECYCLER_VIEW_IMPLEMENTATION = UltronRecyclerViewImpl.PERFORMANCE
UltronConfig.Espresso.ViewActionConfig.autoScroll = true
UltronConfig.applyRecommended()
UltronComposeConfig.applyRecommended()
UltronCommonConfig.addListener(ComposDebugListener())
UltronAllureConfig.applyRecommended()
UltronAllureConfig.setAllureResultsDirectory(Environment.DIRECTORY_DOWNLOADS)
}
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/UiElementsTest.kt
================================================
package com.atiurin.sampleapp.tests
import com.atiurin.sampleapp.activity.UiElementsActivity
import com.atiurin.ultron.testlifecycle.activity.UltronActivityRule
abstract class UiElementsTest : BaseTest() {
val activityRule = UltronActivityRule(UiElementsActivity::class.java)
init {
ruleSequence.add(activityRule)
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/CheckboxTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import androidx.compose.material.TriStateCheckbox
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.test.hasContentDescription
import com.atiurin.ultron.core.compose.createDefaultUltronComposeRule
import com.atiurin.ultron.core.compose.nodeinteraction.click
import com.atiurin.ultron.extensions.assertIsIndeterminate
import org.junit.Rule
import org.junit.Test
class CheckboxTest {
@get:Rule
val composeRule = createDefaultUltronComposeRule()
@Test
fun checkboxStates() {
val testTag = "checkBox"
composeRule.setContent {
val checkedState = remember { mutableStateOf(ToggleableState.Indeterminate) }
TriStateCheckbox(
state = checkedState.value,
onClick = {
if (checkedState.value == ToggleableState.Indeterminate || checkedState.value == ToggleableState.Off)
checkedState.value = ToggleableState.On
else checkedState.value = ToggleableState.Off
},
modifier = Modifier.semantics { contentDescription = testTag }
)
}
hasContentDescription(testTag).assertIsIndeterminate()
.click().assertIsOn()
.click().assertIsOff()
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/CollectionInteractionTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import androidx.compose.ui.test.hasTestTag
import com.atiurin.sampleapp.activity.ComposeListActivity
import com.atiurin.sampleapp.compose.contactNameTestTag
import com.atiurin.sampleapp.compose.contactsListTestTag
import com.atiurin.sampleapp.data.repositories.CONTACTS
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.core.compose.operation.UltronComposeCollectionInteraction.Companion.allNodes
import org.junit.Rule
import org.junit.Test
class CollectionInteractionTest: BaseTest() {
@get:Rule
val composeRule = createSimpleUltronComposeRule()
val list = hasTestTag(contactsListTestTag)
@Test
fun allNodes_getByIndex(){
val index = 4
val contact = CONTACTS[index]
allNodes(hasTestTag(contactNameTestTag), true).get(index).assertTextContains(contact.name)
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/ComposeConfigTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import com.atiurin.sampleapp.activity.ComposeElementsActivity
import com.atiurin.sampleapp.framework.DummyMetaObject
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.pages.ComposeElementsPage
import com.atiurin.ultron.core.compose.config.UltronComposeConfig
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.extensions.assertExists
import com.atiurin.ultron.extensions.isSuccess
import com.atiurin.ultron.extensions.withResultHandler
import com.atiurin.ultron.extensions.withTimeout
import com.atiurin.ultron.core.compose.operation.ComposeOperationResult
import com.atiurin.ultron.core.compose.operation.ComposeOperationType
import com.atiurin.ultron.core.compose.operation.UltronComposeOperation
import com.atiurin.ultron.extensions.*
import com.atiurin.ultron.testlifecycle.rulesequence.RuleSequence
import com.atiurin.ultron.testlifecycle.setupteardown.SetUp
import com.atiurin.ultron.testlifecycle.setupteardown.SetUpRule
import com.atiurin.ultron.testlifecycle.setupteardown.TearDown
import com.atiurin.ultron.testlifecycle.setupteardown.TearDownRule
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
class ComposeConfigTest {
companion object {
const val setCustomTimeout = "Set custom timeout"
const val dropCustomTimeout = "Drop custom timeout"
const val customTimeout = 1100L
}
val page = ComposeElementsPage
val composeRule = createSimpleUltronComposeRule()
val setUpRule = SetUpRule().add(setCustomTimeout) { UltronComposeConfig.params.operationTimeoutMs = customTimeout }
val tearDownRule = TearDownRule().add(dropCustomTimeout) { UltronComposeConfig.params.operationTimeoutMs = UltronComposeConfig.DEFAULT_OPERATION_TIMEOUT }
@get:Rule
val ruleSequence = RuleSequence().add(composeRule, setUpRule, tearDownRule)
@Test
fun resultHandler_successOperation() {
lateinit var result: ComposeOperationResult
page.editableText.withResultHandler { result = it }.assertTextContains("")
Assert.assertTrue(result.success)
Assert.assertTrue(result.operation.type == ComposeOperationType.CONTAINS_TEXT)
}
@Test
fun resultHandler_failedOperation() {
lateinit var result: ComposeOperationResult
page.editableText.withResultHandler { result = it }.withTimeout(100).assertTextContains("invalid")
Assert.assertFalse(result.success)
Assert.assertTrue(result.operation.type == ComposeOperationType.CONTAINS_TEXT)
Assert.assertTrue(result.exceptions.isNotEmpty())
Assert.assertTrue(result.description.isNotEmpty())
}
@Test
@SetUp(setCustomTimeout)
@TearDown(dropCustomTimeout)
fun operationTimeout() {
page.likesCounter.assertIsDisplayed()
AssertUtils.assertExecTimeBetween(customTimeout, 4900) {
page.likesCounter.assertTextContains("asdqw3213")
}
}
@Test
fun isSuccess_false() {
Assert.assertFalse(page.editableText.isSuccess { withTimeout(100).assertDoesNotExist() })
}
@Test
fun isSuccess_true() {
Assert.assertTrue(page.editableText.isSuccess { assertExists() })
}
@Test
fun withName_inOperationProps_ultronInteraction() {
val name = "ElementName"
page.notExistedElement.withTimeout(100).withName(name).withResultHandler { result ->
Assert.assertEquals(name, result.operation.elementInfo.name)
}.assertIsDisplayed()
}
@Test
fun withName_inOperationProps_matcherExt() {
val name = "ElementName"
page.notExistedElement.withName(name).withTimeout(100).withResultHandler { result ->
Assert.assertEquals(name, result.operation.elementInfo.name)
}.assertIsDisplayed()
}
@Test
fun withName_inExceptionMessage() {
val name = "ElementNameToBeInException"
runCatching {
page.notExistedElement.withTimeout(100).withName(name).assertIsDisplayed()
}.onFailure { exception ->
Assert.assertTrue(exception.message!!.contains(name))
}
}
@Test
fun withMeta() {
val meta = DummyMetaObject("ElementMetaInfo")
page.notExistedElement.withTimeout(100).withMetaInfo(meta).withResultHandler { result ->
Assert.assertEquals(meta, result.operation.elementInfo.meta)
}.assertIsDisplayed()
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/ComposeCustomAssertionTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import com.atiurin.sampleapp.activity.ComposeElementsActivity
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.pages.ComposeElementsPage
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.extensions.withAssertion
import com.atiurin.ultron.extensions.withTimeout
import com.atiurin.ultron.core.compose.nodeinteraction.click
import org.junit.Rule
import org.junit.Test
class ComposeCustomAssertionTest : BaseTest() {
val page = ComposeElementsPage
@get:Rule
val composeRule = createSimpleUltronComposeRule()
@Test
fun validAssertion(){
page.likesCounter.withAssertion {
page.likesCounter.withTimeout(100).assertTextEquals("Like count = 3")
}.click()
}
@Test
fun invalidAssertion(){
AssertUtils.assertException {
page.likesCounter.withTimeout(1000).withAssertion {
page.likesCounter.withTimeout(500).assertTextEquals("some invalid text")
}.click()
}
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/ComposeEmptyListTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.test.hasTestTag
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.core.compose.createDefaultUltronComposeRule
import com.atiurin.ultron.core.compose.list.composeList
import org.junit.Rule
import org.junit.Test
class ComposeEmptyListTest : BaseTest() {
@get:Rule
val composeRule = createDefaultUltronComposeRule()
private val emptyListTestTag = "emptyList"
@Test
fun assertNotEmpty_emptyList() {
setEmptyListContent()
AssertUtils.assertException {
composeList(hasTestTag(emptyListTestTag)).withTimeout(100).assertNotEmpty()
}
}
@Test
fun assertEmpty_emptyList() {
setEmptyListContent()
composeList(hasTestTag(emptyListTestTag)).assertEmpty()
}
private fun setEmptyListContent() {
composeRule.setContent {
LazyColumn(
modifier = Modifier.semantics { testTag = emptyListTestTag }
) {}
}
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/ComposeListTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import androidx.compose.ui.test.hasAnyDescendant
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.performScrollToIndex
import com.atiurin.sampleapp.activity.ComposeListActivity
import com.atiurin.sampleapp.compose.contactStatusTestTag
import com.atiurin.sampleapp.compose.contactsListContentDesc
import com.atiurin.sampleapp.compose.contactsListHeaderTag
import com.atiurin.sampleapp.compose.contactsListTestTag
import com.atiurin.sampleapp.compose.getContactItemTestTagById
import com.atiurin.sampleapp.data.repositories.CONTACTS
import com.atiurin.sampleapp.data.repositories.ContactRepositoty
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.pages.ComposeListPage
import com.atiurin.sampleapp.pages.ComposeSecondPage
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.core.common.options.ContentDescriptionContainsOption
import com.atiurin.ultron.core.common.options.TextContainsOption
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.core.compose.list.composeList
import com.atiurin.ultron.extensions.assertIsDisplayed
import com.atiurin.ultron.extensions.assertTextEquals
import com.atiurin.ultron.extensions.click
import com.atiurin.ultron.extensions.findNodeInTree
import com.atiurin.ultron.extensions.withDescription
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
class ComposeListTest : BaseTest() {
@get:Rule
val composeRule = createSimpleUltronComposeRule()
private val listWithMergedTree = composeList(hasTestTag(contactsListTestTag), false).withDescription("Contacts list")
private val listPage = ComposeListPage
private val notExistedList = composeList(hasTestTag("askjhsalk jdhas dlqk "))
private val emptyListTestTag = "emptyList"
@Test
fun item_existItem() {
val index = 20
val contact = CONTACTS[index]
listWithMergedTree.assertIsDisplayed()
listWithMergedTree.item(hasAnyDescendant(hasText(contact.name)).withDescription("Contact '${contact.name}'"))
.assertIsDisplayed()
.assertMatches(hasAnyDescendant(hasText(contact.name)).withDescription("Contact '${contact.name}'"))
}
@Test
fun item_notExistItem() {
AssertUtils.assertException {
listWithMergedTree.item(hasText("123gakshgdasl kgas")).assertIsDisplayed()
}
}
@Test
fun visibleItem_indexInScope() {
val index = 2
val contact = CONTACTS[index]
listWithMergedTree.visibleItem(index).printToLog("ULTRON")
.assertMatches(hasAnyDescendant(hasText(contact.name)))
}
@Test
fun visibleItem_indexOutOfScope() {
AssertUtils.assertException {
listWithMergedTree.visibleItem(20).assertIsDisplayed()
}
}
@Test
fun firstVisibleItem() {
val contact = CONTACTS[0]
listWithMergedTree.firstVisibleItem()
.assertIsDisplayed()
.assertMatches(hasAnyDescendant(hasText(contact.name)))
.assertMatches(hasAnyDescendant(hasText(contact.status)))
}
@Test
fun getVisibleItemByIndex() {
val index = 3
val contact = CONTACTS[index]
listPage.getItemByIndex(index).apply {
name.assertTextEquals(contact.name)
status.assertTextEquals(contact.status)
}
}
@Test
fun getFirstVisibleItem() {
val contact = CONTACTS[0]
listPage.getFirstVisibleItem().apply {
name.assertTextEquals(contact.name)
status.assertTextEquals(contact.status)
}
}
@Test
fun scrollToIndex() {
val index = 20
val contact = CONTACTS[index]
listWithMergedTree.scrollToIndex(index)
hasText(contact.name).assertIsDisplayed()
}
@Test
fun scrollToKey() {
val index = 20
val contact = CONTACTS[index]
listWithMergedTree.scrollToKey(contact.name)
hasText(contact.name).assertIsDisplayed()
}
@Test
fun customPerformOnLazyList() {
val index = 20
val contact = CONTACTS[index]
val children = listWithMergedTree.performOnList { node, interactoion ->
interactoion.performScrollToIndex(index)
node.children
}
hasText(contact.name).assertIsDisplayed()
Assert.assertTrue(children.size > 10)
val child = children.findNodeInTree(hasText(contact.name))
Assert.assertNotNull(child)
}
@Test
fun moveToAnotherComposeActivityPageTest() {
val contact = CONTACTS.first()
hasText(contact.name).click()
ComposeSecondPage.name.assertTextEquals(contact.name)
ComposeSecondPage.status.assertTextEquals(contact.status)
}
@Test
fun getItem_ByTestTag_assertNameAndStatusOfContact() {
val index = 20
val contact = CONTACTS[index]
listPage.getContactItemByTestTag(contact).apply {
name.assertTextEquals(contact.name)
status.assertTextContains(contact.status)
}
}
@Test
fun getItem_ByMatcher_assertNameAndStatusOfContact() {
val index = 20
val contact = CONTACTS[index]
listPage.getContactItemByName(contact).apply {
name.assertTextEquals(contact.name)
status.assertTextContains(contact.status)
}
}
@Test
fun listHeader_asChild_TextContains() {
listPage.lazyList.visibleChild(hasTestTag(contactsListHeaderTag))
.assertTextContains("header", option = TextContainsOption(substring = true))
}
@Test
fun listHeader_asChild_TextEquals() {
listPage.lazyList.visibleChild(hasTestTag(contactsListHeaderTag).withDescription("header"))
.assertTextEquals("Lazy column header")
}
@Test
fun listHeader_asChild_TextEquals_mergedTree() {
listWithMergedTree.visibleChild(hasTestTag(contactsListHeaderTag).withDescription("header"))
.assertTextEquals("Lazy column header")
}
@Test
fun visibleItemChild() {
val index = 3
val contact = CONTACTS[index]
listPage.lazyList.onVisibleItemChild(index, hasTestTag(contactStatusTestTag)).assertTextEquals(contact.status)
}
@Test
fun assertIsDisplayed_visibleList() {
listWithMergedTree.assertIsDisplayed()
}
@Test
fun assertIsDisplayed_invisibleList() {
AssertUtils.assertException {
notExistedList.withTimeout(1000).assertIsDisplayed()
}
}
@Test
fun assertIsNotDisplayed_visibleList() {
AssertUtils.assertException { listWithMergedTree.withTimeout(1000).assertIsNotDisplayed() }
}
@Test
fun assertExists_existedList() {
listWithMergedTree.assertExists()
}
@Test
fun assertExists_notExistedList() {
AssertUtils.assertException { notExistedList.withTimeout(1000).assertExists() }
}
@Test
fun assertDoesNotExist_notExistedList() {
notExistedList.assertDoesNotExist()
}
@Test
fun assertDoesNotExist_existedList() {
AssertUtils.assertException { listWithMergedTree.withTimeout(1000).assertDoesNotExist() }
}
@Test
fun assertContentDescriptionEquals_properContentDescription() {
listWithMergedTree.assertContentDescriptionEquals(contactsListContentDesc)
}
@Test
fun assertContentDescriptionEquals_invalidContentDescription() {
AssertUtils.assertException { listWithMergedTree.withTimeout(1000).assertContentDescriptionEquals("some invalid desc") }
}
@Test
fun assertContentDescriptionContains_properContentDescription() {
listWithMergedTree.assertContentDescriptionContains(contactsListContentDesc.substring(0, 5), ContentDescriptionContainsOption(substring = true))
}
@Test
fun assertContentDescriptionContains_invalidContentDescription() {
AssertUtils.assertException { listWithMergedTree.withTimeout(1000).assertContentDescriptionContains("some invalid") }
}
@Test
fun assertVisibleItemsCount_properCountProvided() {
val count = listWithMergedTree.getVisibleItemsCount()
listWithMergedTree.assertVisibleItemsCount(count)
}
@Test
fun assertVisibleItemsCount_invalidCountProvided() {
AssertUtils.assertException { listWithMergedTree.withTimeout(1000).assertVisibleItemsCount(100) }
}
@Test
fun itemByPosition_propertyConfiguredTest() {
val index = 20
val contact = CONTACTS[index]
val item = listPage.lazyList.item(20).assertIsDisplayed()
item.assertMatches(hasTestTag(getContactItemTestTagById(contact)))
}
@Test
fun getItemByPosition_propertyConfiguredTest() {
val index = 20
val contact = CONTACTS[index]
listPage.getItemByPosition(index).apply {
name.assertTextEquals(contact.name)
status.assertTextEquals(contact.status)
assertIsDisplayed()
}
}
@Test
fun assertItemDoesNotExistWithSearch_NotExistedItem() {
listWithMergedTree.assertItemDoesNotExist(hasText("NOT EXISTED TeXT"))
}
@Test
fun assertItemDoesNotExistWithSearch_ExistedItem() {
val contact = ContactRepositoty.getLast()
AssertUtils.assertException {
listWithMergedTree.withTimeout(2000).assertItemDoesNotExist(hasText(contact.name))
}
}
@Test
fun getItem_NotExistedItemChild() {
val index = 20
val contact = CONTACTS[index]
listPage.getContactItemByName(contact).apply {
AssertUtils.assertException { notExisted.withTimeout(1000).assertIsDisplayed() }
}
}
@Test
fun assertNotEmpty_notEmptyList() {
listWithMergedTree.assertNotEmpty()
}
@Test
fun assertEmpty_notEmptyList() {
AssertUtils.assertException { listWithMergedTree.withTimeout(100).assertEmpty() }
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/ComposeListWithPositionTestTagTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import androidx.compose.ui.test.hasAnyDescendant
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import com.atiurin.sampleapp.activity.ComposeListWithPositionTestTagActivity
import com.atiurin.sampleapp.compose.ListItemPositionPropertyKey
import com.atiurin.sampleapp.compose.contactsListContentDesc
import com.atiurin.sampleapp.compose.getContactItemTestTagByPosition
import com.atiurin.sampleapp.data.repositories.CONTACTS
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.pages.ComposeListPage
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.core.compose.list.composeList
import org.junit.Rule
import org.junit.Test
class ComposeListWithPositionTestTagTest {
@get:Rule
val composeRule = createSimpleUltronComposeRule()
val list = composeList(hasContentDescription(contactsListContentDesc), false)
val composeListWithProperty = composeList(hasContentDescription(contactsListContentDesc), false, ListItemPositionPropertyKey)
@Test
fun itemOutOfVisibleScope() {
val index = 20
val contact = CONTACTS[index]
list.item(hasTestTag(getContactItemTestTagByPosition(index)))
.assertIsDisplayed()
.assertMatches(hasAnyDescendant(hasText(contact.name)))
}
@Test
fun lastVisibleItem() {
val count = list.getVisibleItemsCount() - 1
val contact = CONTACTS[count]
list.lastVisibleItem()
.assertIsDisplayed()
.assertMatches(hasAnyDescendant(hasText(contact.name)))
.assertMatches(hasAnyDescendant(hasText(contact.status)))
}
@Test
fun itemByPosition_propertyNOTConfiguredInTest() {
AssertUtils.assertException {
list.item(20).assertIsDisplayed()
}
}
@Test
fun itemByPosition_propertyNOTConfiguredInApplication() {
AssertUtils.assertException {
composeListWithProperty.withTimeout(1000).item(20).assertIsDisplayed()
}
}
@Test
fun getItemByPosition_propertyNOTConfiguredInTest() {
AssertUtils.assertException { list.getItem(20).assertIsDisplayed() }
}
@Test
fun getItemByPosition_propertyNOTConfiguredInApplication() {
AssertUtils.assertException {
composeListWithProperty.withTimeout(1000).getItem(20).assertIsDisplayed()
}
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/ComposeUIElementsTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertTextContains
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.unit.dp
import com.atiurin.sampleapp.activity.ActionsStatus
import com.atiurin.sampleapp.activity.ComposeElementsActivity
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.likesCounterButton
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.likesCounterContentDesc
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.likesCounterTextContainerContentDesc
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.simpleCheckbox
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.statusText
import com.atiurin.sampleapp.compose.RegionName
import com.atiurin.sampleapp.framework.ultronext.ProgressBar
import com.atiurin.sampleapp.framework.ultronext.getProgress
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.pages.ComposeElementsPage
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.core.common.assertion.softAssertion
import com.atiurin.ultron.core.common.assertion.verifySoftAssertions
import com.atiurin.ultron.core.common.options.ClickOption
import com.atiurin.ultron.core.common.options.ContentDescriptionContainsOption
import com.atiurin.ultron.core.common.options.PerformCustomBlockOption
import com.atiurin.ultron.core.common.options.TextContainsOption
import com.atiurin.ultron.core.common.options.TextEqualsOption
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.core.compose.nodeinteraction.UltronComposeSemanticsNodeInteraction
import com.atiurin.ultron.core.compose.nodeinteraction.click
import com.atiurin.ultron.core.compose.operation.ComposeOperationType
import com.atiurin.ultron.core.compose.operation.UltronComposeCollectionInteraction.Companion.allNodes
import com.atiurin.ultron.core.compose.operation.UltronComposeOperationParams
import com.atiurin.ultron.core.compose.operation.assertSize
import com.atiurin.ultron.core.compose.option.ComposeSwipeOption
import com.atiurin.ultron.core.config.UltronCommonConfig
import com.atiurin.ultron.extensions.assertContentDescriptionContains
import com.atiurin.ultron.extensions.assertContentDescriptionEquals
import com.atiurin.ultron.extensions.assertDoesNotExist
import com.atiurin.ultron.extensions.assertExists
import com.atiurin.ultron.extensions.assertHasClickAction
import com.atiurin.ultron.extensions.assertHasNoClickAction
import com.atiurin.ultron.extensions.assertHeightIsAtLeast
import com.atiurin.ultron.extensions.assertHeightIsEqualTo
import com.atiurin.ultron.extensions.assertIsDisplayed
import com.atiurin.ultron.extensions.assertIsEnabled
import com.atiurin.ultron.extensions.assertIsNotEnabled
import com.atiurin.ultron.extensions.assertIsNotFocused
import com.atiurin.ultron.extensions.assertIsNotSelected
import com.atiurin.ultron.extensions.assertIsOff
import com.atiurin.ultron.extensions.assertIsSelectable
import com.atiurin.ultron.extensions.assertIsToggleable
import com.atiurin.ultron.extensions.assertTextContains
import com.atiurin.ultron.extensions.assertTextEquals
import com.atiurin.ultron.extensions.assertValueEquals
import com.atiurin.ultron.extensions.assertWidthIsAtLeast
import com.atiurin.ultron.extensions.assertWidthIsEqualTo
import com.atiurin.ultron.extensions.captureToImage
import com.atiurin.ultron.extensions.clearText
import com.atiurin.ultron.extensions.click
import com.atiurin.ultron.extensions.clickBottomCenter
import com.atiurin.ultron.extensions.clickBottomLeft
import com.atiurin.ultron.extensions.clickBottomRight
import com.atiurin.ultron.extensions.clickCenterLeft
import com.atiurin.ultron.extensions.clickCenterRight
import com.atiurin.ultron.extensions.clickTopCenter
import com.atiurin.ultron.extensions.clickTopLeft
import com.atiurin.ultron.extensions.clickTopRight
import com.atiurin.ultron.extensions.copyText
import com.atiurin.ultron.extensions.doubleClick
import com.atiurin.ultron.extensions.execute
import com.atiurin.ultron.extensions.getNode
import com.atiurin.ultron.extensions.getNodeConfigProperty
import com.atiurin.ultron.extensions.getText
import com.atiurin.ultron.extensions.inputText
import com.atiurin.ultron.extensions.longClick
import com.atiurin.ultron.extensions.pasteText
import com.atiurin.ultron.extensions.perform
import com.atiurin.ultron.extensions.performMouseInput
import com.atiurin.ultron.extensions.replaceText
import com.atiurin.ultron.extensions.selectText
import com.atiurin.ultron.extensions.swipe
import com.atiurin.ultron.extensions.swipeDown
import com.atiurin.ultron.extensions.swipeLeft
import com.atiurin.ultron.extensions.swipeRight
import com.atiurin.ultron.extensions.swipeUp
import com.atiurin.ultron.extensions.typeText
import com.atiurin.ultron.extensions.withTimeout
import org.junit.Assert
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@OptIn(ExperimentalTestApi::class)
class ComposeUIElementsTest : BaseTest() {
val page = ComposeElementsPage
@get:Rule
val composeRule = createSimpleUltronComposeRule()
val initialText = "Like count = 0"
val expectedText = "Like count = 1"
@Test
fun simpleClick() {
hasText(initialText).assertIsDisplayed().click()
hasText(expectedText).assertIsDisplayed()
}
@Test
fun contentDescTest() {
hasContentDescription(likesCounterContentDesc).click()
hasText(expectedText).assertIsDisplayed()
}
@Test
fun testTagTest() {
hasText(initialText).click()
page.likesCounter.assertTextEquals(expectedText).assertIsDisplayed()
}
@Test
fun getTextTest() {
hasTestTag(likesCounterButton).click()
val text = hasTestTag(likesCounterButton).getText()
Assert.assertEquals(expectedText, text)
}
@Test
fun clickCheckBox() {
hasTestTag(simpleCheckbox).assertIsOff().click().assertIsOn()
}
@Test
fun longClick_longClickable() {
page.longAndDoubleClickButton.longClick()
page.status.assertTextEquals(ActionsStatus.LongClicked.name)
}
@Test
fun doubleClick_doubleClickable() {
page.longAndDoubleClickButton.doubleClick()
page.status.assertTextEquals(ActionsStatus.DoubleClicked.name)
}
@Test
fun regionsClickTopLeft() {
page.regionsNode.clickTopLeft(ClickOption(xOffset = 20, yOffset = 20))
page.status.assertTextEquals(RegionName.TopLeft.name)
}
@Test
fun regionsClickTopCenter() {
page.regionsNode.clickTopCenter(ClickOption(yOffset = 20))
page.status.assertTextEquals(RegionName.TopCenter.name)
}
@Test
fun regionsClickTopRight() {
page.regionsNode.clickTopRight(ClickOption(yOffset = 20))
page.status.assertTextEquals(RegionName.TopRight.name)
}
@Test
fun regionsClickCenterLeft() {
page.regionsNode.clickCenterLeft()
page.status.assertTextEquals(RegionName.CenterLeft.name)
}
@Test
fun regionsClickCenterRight() {
page.regionsNode.clickCenterRight()
page.status.assertTextEquals(RegionName.CenterRight.name)
}
@Test
fun regionsClickBottomLeft() {
page.regionsNode.clickBottomLeft(ClickOption(xOffset = 16))
page.status.assertTextEquals(RegionName.BottomLeft.name)
}
@Test
fun regionsClickBottomCenter() {
page.regionsNode.clickBottomCenter()
page.status.assertTextEquals(RegionName.BottomCenter.name)
}
@Test
fun regionsClickBottomRight() {
page.regionsNode.clickBottomRight()
page.status.assertTextEquals(RegionName.BottomRight.name)
}
//flaky on emulator
@Test
@Ignore
fun copyText() {
val startText = "begin"
page.editableText.apply {
replaceText(startText)
selectText(TextRange(0, 2))
assertIsDisplayed()
copyText()
assertIsDisplayed()
clearText()
click()
pasteText()
assertTextContains(startText)
}
}
@Test
fun swipeDown() {
page.swipeableNode.swipeDown()
page.status.assertTextEquals(ActionsStatus.SwipeDown.name)
}
@Test
fun swipeUp() {
page.swipeableNode.swipeUp()
page.status.assertTextEquals(ActionsStatus.SwipeUp.name)
}
@Test
fun swipeRight() {
page.swipeableNode.swipeRight()
page.status.assertTextEquals(ActionsStatus.SwipeRight.name)
}
@Test
fun swipeLeft() {
page.swipeableNode.swipeLeft()
page.status.assertTextEquals(ActionsStatus.SwipeLeft.name)
}
@Test
fun swipe_option() {
page.swipeableNode.swipeLeft(ComposeSwipeOption(durationMs = 1000L))
page.status.assertTextEquals(ActionsStatus.SwipeLeft.name)
}
@Test
fun swipe_general() {
page.swipeableNode.swipe(ComposeSwipeOption(
startXOffset = 0.1f,
startYOffset = 0.1f,
endXOffset = 0.9f,
endYOffset = 0.1f,
durationMs = 1000L
))
page.status.assertTextEquals(ActionsStatus.SwipeRight.name)
}
@Test
fun inputText() {
val text = "some text"
page.editableText.inputText(text).assertTextContains(text)
}
@Test
fun typeText() {
val text = "some text"
page.editableText.typeText(text).assertTextContains(text)
}
@Test
fun setSelection() {
page.editableText.replaceText("qwerty").setSelection(0, 3, true).assertIsDisplayed().cutText().assertTextContains("rty")
}
@Test
fun captureToImage() {
val image = page.longAndDoubleClickButton.captureToImage()
Assert.assertNotNull(image)
}
@Test
fun setProgress() {
page.progressBar.setProgress(0.7f).assertIsDisplayed()
page.status.assertTextEquals("set progress 0.7")
}
@Test
fun performCustomSemanticsAction() {
val progress = 0.7f
val progressBar = ProgressBar(hasTestTag(ComposeElementsActivity.progressBar))
progressBar.setProgress(progress)
val current = progressBar.getProgress()
Assert.assertEquals(current, progress )
}
@Test
fun performCustomSemanticsAssertion() {
val progress = 0.7f
val progressBar = ProgressBar(hasTestTag(ComposeElementsActivity.progressBar))
progressBar.setProgress(progress)
progressBar.assertProgress(progress)
}
@Test
fun performExtendedAssertion() {
val progress = 0.7f
page.progressBar.apply {
setProgress(progress)
assertProgress(progress)
}
}
@Test
fun performWithLambda() {
val progress = 0.7f
val result = page.progressBar.perform {
it.performSemanticsAction(SemanticsActions.SetProgress) {
it.invoke(progress)
}
}
Assert.assertTrue(result is UltronComposeSemanticsNodeInteraction)
page.progressBar.assertProgress(progress)
}
@Test
fun semanticsMatcher_performDeprecated() {
val text = page.status.perform({
it.fetchSemanticsNode().config[SemanticsProperties.Text].first().text
}, option = PerformCustomBlockOption(ComposeOperationType.CUSTOM, ""))
Assert.assertTrue(text.isNotBlank())
}
@Test
fun ultronComposeSemanticsNodeInteraction_performDeprecated() {
val text = page.status.assertExists().perform(option = PerformCustomBlockOption(ComposeOperationType.CUSTOM, "")) {
it.fetchSemanticsNode().config[SemanticsProperties.Text].first().text
}
Assert.assertTrue(text.isNotBlank())
}
@Test
fun ultronComposeSemanticsNodeInteraction_execute() {
val text = page.status.assertExists().execute {
it.fetchSemanticsNode().config[SemanticsProperties.Text].first().text
}
Assert.assertTrue(text.isNotBlank())
}
@Test
fun semanticsMatcher_execute() {
val text = page.status.execute {
it.fetchSemanticsNode().config[SemanticsProperties.Text].first().text
}
Assert.assertTrue(text.isNotBlank())
}
@Test
fun performMouseInput() {
page.swipeableNode.performMouseInput { swipeUp() }
page.status.assertTextEquals(ActionsStatus.SwipeUp.name)
}
@Test
fun getNode_exits() {
val node = page.status.getNode()
Assert.assertEquals(ComposeElementsActivity.Constants.statusText, node.config[SemanticsProperties.TestTag])
}
@Test
fun getNodeConfigProperty_exist() {
val testTag = page.status.getNodeConfigProperty(SemanticsProperties.TestTag)
Assert.assertEquals(ComposeElementsActivity.Constants.statusText, testTag)
}
@Test
fun assertIsDisplayed() {
page.status.assertIsDisplayed()
}
@Test
fun assertExists() {
page.status.assertExists()
}
@Test
fun assertExists_notExisted() {
AssertUtils.assertException { hasText("some not existed node").withTimeout(100).assertExists() }
}
@Test
fun assertDoesNotExist() {
hasText("some not existed node").assertDoesNotExist()
}
@Test
fun assertDoesNotExist_existed() {
AssertUtils.assertException { page.editableText.withTimeout(100).assertDoesNotExist() }
}
@Test
fun assertIsEnabled() {
page.editableText.assertIsEnabled()
}
@Test
fun assertIsEnabled_disabledButton() {
AssertUtils.assertException { page.disabledButton.withTimeout(100).assertIsEnabled() }
}
@Test
fun assertIsNotEnabled() {
page.disabledButton.assertIsNotEnabled()
}
@Test
fun assertIsNotEnabled_enabledButton() {
AssertUtils.assertException { page.longAndDoubleClickButton.withTimeout(100).assertIsNotEnabled() }
}
@Test
fun assertIsFocused() {
page.editableText.click().assertIsFocused()
}
@Test
fun assertIsFocused_notFocused() {
AssertUtils.assertException { page.editableText.withTimeout(100).assertIsFocused() }
}
@Test
fun assertIsNotFocused() {
page.editableText.assertIsNotFocused()
}
@Test
fun assertIsNotFocused_focused() {
AssertUtils.assertException { page.editableText.click().withTimeout(100).assertIsNotFocused() }
}
@Test
fun assertIsSelected() {
page.maleRadioButton.click().assertIsSelected()
}
@Test
fun assertIsSelected_notSelected() {
page.maleRadioButton.click()
AssertUtils.assertException { page.femaleRadioButton.withTimeout(100).assertIsSelected() }
}
@Test
fun assertIsNotSelected_notSelected() {
page.maleRadioButton.click()
page.femaleRadioButton.assertIsNotSelected()
}
@Test
fun assertIsNotSelected_selected() {
AssertUtils.assertException { page.maleRadioButton.click().withTimeout(100).assertIsNotSelected() }
}
@Test
fun assertIsSelectable() {
page.femaleRadioButton.assertIsSelectable()
}
@Test
fun assertIsSelectable_notSelectable() {
AssertUtils.assertException { page.status.withTimeout(100).assertIsSelectable() }
}
@Test
fun assertIsToggleable() {
page.simpleCheckbox.assertIsToggleable()
}
@Test
fun assertIsToggleable_notToggleable() {
AssertUtils.assertException { page.editableText.withTimeout(100).assertIsToggleable() }
}
@Test
fun assertIsOn() {
page.simpleCheckbox.click().assertIsOn()
}
@Test
fun assertIsOn_checkboxIsOff() {
AssertUtils.assertException { page.simpleCheckbox.withTimeout(100).assertIsOn() }
}
@Test
fun assertIsOff() {
page.simpleCheckbox.assertIsOff()
}
@Test
fun assertIsOff_checkboxIsOn() {
AssertUtils.assertException { page.simpleCheckbox.click().withTimeout(100).assertIsOff() }
}
@Test
fun assertHasClickAction() {
page.longAndDoubleClickButton.assertHasClickAction()
}
@Test
fun assertHasClickAction_noClickAction() {
AssertUtils.assertException { page.status.withTimeout(100).assertHasClickAction() }
}
@Test
fun assertHasNoClickAction() {
page.status.assertHasNoClickAction()
}
@Test
fun assertHasNoClickAction_hasClickAction() {
AssertUtils.assertException { page.longAndDoubleClickButton.withTimeout(100).assertHasNoClickAction() }
}
@Test
fun assertTextEquals() {
page.editableText.assertTextEquals("Label", "")
}
@Test
fun assertTextEquals_includeEditableFalse() {
page.editableText.assertTextEquals("Label", option = TextEqualsOption(false))
}
@Test
fun assertTextEquals_includeEditableFalse_editableProvided() {
AssertUtils.assertException {
page.editableText.withTimeout(100).assertTextEquals("Label", "", option = TextEqualsOption(false))
}
}
@Test
fun assertTextEquals_wrongTextProvided() {
AssertUtils.assertException {
page.editableText.withTimeout(100).assertTextEquals("some invalid text", "")
}
}
@Test
fun assertTextEquals_editableNotEmpty_ValidText() {
val text = "editable text"
page.editableText.replaceText(text).assertTextEquals("Label", text)
}
@Test
fun assertTextEquals_editableNotEmpty_ValidText_mixedOrder() {
val text = "editable text"
page.editableText.replaceText(text).assertTextEquals(text, "Label")
}
@Test
fun assertTextEquals_editableNotEmpty_includeEditableFalse() {
val text = "editable text"
AssertUtils.assertException {
page.editableText.withTimeout(100).replaceText(text).assertTextEquals("Label", text, option = TextEqualsOption(false))
}
}
@Test
fun assertTextContains_label() {
page.editableText.assertTextContains("Label")
}
@Test
fun assertTextContains_editable() {
val text = "some text"
page.editableText.replaceText(text).assertTextContains(text)
}
@Test
fun assertTextContains_wrongText() {
val text = "some text"
AssertUtils.assertException { page.editableText.withTimeout(100).assertTextContains(text) }
}
@Test
fun assertTextContains_emptyText() {
page.editableText.assertTextContains("")
}
@Test
fun assertTextContains_substringTrue_validSubstringProvided() {
val text = "some text"
page.editableText.replaceText(text).assertTextContains(text.substring(0, 4), TextContainsOption(substring = true))
}
@Test
fun assertTextContains_substringTrue_wrongSubstringProvided() {
AssertUtils.assertException {
page.editableText.replaceText("valid text").withTimeout(100).assertTextContains("wrong text", TextContainsOption(substring = true))
}
}
@Test
fun assertTextContains_substringFalse_validSubstringProvided() {
val text = "some text"
AssertUtils.assertException {
page.editableText.replaceText(text).withTimeout(100).assertTextContains(text.substring(0, 4), TextContainsOption(substring = false))
}
}
@Test
fun assertTextContains_ignoreCase_lowercase() {
val text = "SoMe TexT"
page.editableText.replaceText(text).assertTextContains(text.lowercase(), TextContainsOption(ignoreCase = true))
}
@Test
fun assertTextContains_ignoreCase_uppercase() {
val text = "SoMe TexT"
page.editableText.replaceText(text).assertTextContains(text.uppercase(), TextContainsOption(ignoreCase = true))
}
@Test
fun assertTextContains_ignoreCase_and_substring() {
val text = "SoMe TexT"
page.editableText.replaceText(text).assertTextContains(text.substring(0, 4).lowercase(), TextContainsOption(substring = true, ignoreCase = true))
}
@Test
fun assertTextContains_ignoreCaseFalse() {
val text = "SoMe TexT"
AssertUtils.assertException {
page.editableText.replaceText(text).withTimeout(100).assertTextContains(text.lowercase(), TextContainsOption(ignoreCase = false))
}
}
@Test
fun assertContentDescriptionEquals() {
page.likesCounter.assertContentDescriptionEquals(likesCounterContentDesc, likesCounterTextContainerContentDesc)
}
@Test
fun assertContentDescriptionEquals_notEnoughElements() {
AssertUtils.assertException {
page.likesCounter.withTimeout(100).assertContentDescriptionEquals(likesCounterContentDesc)
}
}
@Test
fun assertContentDescriptionContains() {
page.likesCounter.assertContentDescriptionContains(likesCounterContentDesc)
}
@Test
fun assertContentDescriptionContains_substringTrue_validSubstringProvided() {
page.likesCounter.assertContentDescriptionContains(likesCounterContentDesc.substring(1, 5), ContentDescriptionContainsOption(substring = true))
}
@Test
fun assertContentDescriptionContains_substringTrue_wrongSubstringProvided() {
AssertUtils.assertException {
page.likesCounter.withTimeout(100).assertContentDescriptionContains("wrong substring", ContentDescriptionContainsOption(substring = true))
}
}
@Test
fun assertContentDescriptionContains_ignoreCaseTrue_lowercase() {
page.likesCounter.assertContentDescriptionContains(likesCounterContentDesc.lowercase(), ContentDescriptionContainsOption(ignoreCase = true))
}
@Test
fun assertContentDescriptionContains_ignoreCaseTrue_uppercase() {
page.likesCounter.assertContentDescriptionContains(likesCounterContentDesc.uppercase(), ContentDescriptionContainsOption(ignoreCase = true))
}
@Test
fun assertContentDescriptionContains_ignoreCaseFalse() {
AssertUtils.assertException {
page.likesCounter.withTimeout(100).assertContentDescriptionContains(likesCounterContentDesc.lowercase(), ContentDescriptionContainsOption(ignoreCase = false))
}
}
@Test
fun assertValueEquals() {
page.simpleCheckbox.assertValueEquals("default")
}
@Test
fun assertValueEquals_invalidValue() {
AssertUtils.assertException { page.simpleCheckbox.withTimeout(100).assertValueEquals("invalid") }
}
@Test
fun assertRangeInfoEquals() {
page.progressBar.setProgress(0.7f).assertRangeInfoEquals(ProgressBarRangeInfo(0.7f, range = 0f..0.7f, 100))
}
@Test
fun assertRangeInfoEquals_invalidInfo() {
AssertUtils.assertException {
page.progressBar.setProgress(0.7f).withTimeout(100).assertRangeInfoEquals(ProgressBarRangeInfo(0.0f, range = 0f..0.0f, 100))
}
}
@Test
fun assertHeightIsEqualTo() {
page.swipeableNode.assertHeightIsEqualTo(100.dp)
}
@Test
fun assertHeightIsEqualTo_invalidValue() {
AssertUtils.assertException { page.swipeableNode.withTimeout(100).assertHeightIsEqualTo(50.dp) }
}
@Test
fun assertWidthIsEqualTo() {
page.swipeableNode.assertWidthIsEqualTo(100.dp)
}
@Test
fun assertWidthIsEqualTo_invalidValue() {
AssertUtils.assertException { page.swipeableNode.withTimeout(100).assertWidthIsEqualTo(50.dp) }
}
@Test
fun assertHeightIsAtLeast() {
page.swipeableNode.assertHeightIsAtLeast(10.dp)
}
@Test
fun assertHeightIsAtLeast_invalidValue() {
AssertUtils.assertException { page.swipeableNode.withTimeout(100).assertHeightIsAtLeast(500.dp) }
}
@Test
fun assertWidthIsAtLeast() {
page.swipeableNode.assertWidthIsAtLeast(10.dp)
}
@Test
fun assertWidthIsAtLeast_invalidValue() {
AssertUtils.assertException { page.swipeableNode.withTimeout(100).assertWidthIsAtLeast(500.dp) }
}
@Test
fun assertMatches() {
val text = "some text"
page.editableText.replaceText(text).assertMatches(hasText(text))
}
@Test
fun assertMatches_invalid() {
AssertUtils.assertException {
page.editableText.replaceText("some text").withTimeout(100)
.assertMatches(hasText("invalid text"))
}
}
@Test
fun customPerformParamsMapping() {
val params = UltronComposeOperationParams(
operationName = "operationName",
operationDescription = "operationDescription",
operationType = ComposeOperationType.ASSERT_MATCHES
)
page.status.withTimeout(100).withResultHandler {
val op = it.operation
Assert.assertEquals(params.operationName, op.name)
Assert.assertEquals(params.operationDescription, op.description)
Assert.assertEquals(params.operationType, op.type)
}.perform(params) {
it.assertTextContains("Some invalid text")
}
}
@Test
fun softAssertionTest() {
UltronCommonConfig.testContext.softAnalyzer.clear()
softAssertion(false) {
hasText("NotExistText").withTimeout(100).assertIsDisplayed()
hasTestTag("NotExistTestTag").withTimeout(100).assertHasClickAction()
}
runCatching {
verifySoftAssertions()
}.onFailure { exception ->
val message = exception.message ?: throw RuntimeException("Empty exception message: $exception")
Assert.assertTrue(message.contains("NotExistText"))
Assert.assertTrue(message.contains("NotExistTestTag"))
}
}
@Test
fun allNodesTest_invalidExpectedValue(){
AssertUtils.assertException { allNodes(hasTestTag(statusText)).assertSize(2, operationTimeoutMs = 1000) }
}
@Test
fun allNodesTest_correctExpectedValue(){
allNodes(hasTestTag(statusText)).assertSize(1)
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/DefaultComponentActivityTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import androidx.compose.material.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.test.hasTestTag
import com.atiurin.ultron.core.compose.createDefaultUltronComposeRule
import com.atiurin.ultron.extensions.assertIsDisplayed
import org.junit.Rule
import org.junit.Test
class DefaultComponentActivityTest {
@get:Rule
val composeRule = createDefaultUltronComposeRule()
@Test
fun setContent() {
val testTagValue = "testTag"
composeRule.setContent {
Text(text = "Hello, world!", modifier = Modifier.semantics { testTag = testTagValue })
}
hasTestTag(testTagValue)
.assertIsDisplayed()
.assertTextEquals("Hello, world!")
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/RunUltronUiTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.hasTestTag
import com.atiurin.ultron.core.compose.runUltronUiTest
import com.atiurin.ultron.extensions.assertTextContains
import com.atiurin.ultron.extensions.isSuccess
import org.junit.Test
import kotlin.test.assertTrue
@OptIn(ExperimentalTestApi::class)
class RunUltronUiTest {
@Test
fun useUnmergedTreeConfigTest() = runUltronUiTest {
val testTag = "element"
setContent {
Column {
Button(onClick = {}, modifier = Modifier.testTag(testTag)) {
Text("Text1")
Text("Text2")
}
}
}
assertTrue ("Ultron operation success should be true") {
hasTestTag(testTag).isSuccess { assertTextContains("Text1") }
}
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/SampleClassTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import androidx.compose.ui.test.hasTestTag
import com.atiurin.sampleapp.activity.ComposeElementsActivity
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.extensions.assertIsDisplayed
import com.atiurin.ultron.extensions.withName
import com.atiurin.ultron.extensions.withTimeout
import com.atiurin.ultron.page.Page
import org.junit.Rule
import org.junit.Test
class SampleClassTest : BaseTest() {
@get:Rule
val composeRuleBase = createSimpleUltronComposeRule()
@Test
fun test() {
SomePage{
elementWithName.assertIsDisplayed()
elementWithTimeout.assertIsDisplayed()
elementMatcher.assertIsDisplayed()
}
}
@Test
fun test2() {
SomePage{
elementWithName.assertIsDisplayed()
elementWithTimeout.assertIsDisplayed()
elementMatcher.assertIsDisplayed()
}
}
}
object SomePage : Page() {
val elementWithName = hasTestTag("statusText").withName("sample element name")
val elementWithTimeout = hasTestTag("statusText").withTimeout(4000)
val elementMatcher = hasTestTag("statusText")
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/SemNodeInteractionObjectTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import com.atiurin.sampleapp.activity.ActionsStatus
import com.atiurin.sampleapp.activity.ComposeElementsActivity
import com.atiurin.sampleapp.pages.ComposeElementsPage
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.core.common.options.TextContainsOption
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.core.compose.nodeinteraction.click
import com.atiurin.ultron.core.compose.nodeinteraction.doubleClick
import com.atiurin.ultron.core.compose.nodeinteraction.longClick
import com.atiurin.ultron.core.compose.option.ComposeSwipeOption
import com.atiurin.ultron.extensions.assertTextEquals
import com.atiurin.ultron.extensions.withMetaInfo
import com.atiurin.ultron.extensions.withName
import org.junit.Rule
import org.junit.Test
class SemNodeInteractionObjectTest : BaseTest() {
@get:Rule
val composeRule = createSimpleUltronComposeRule()
val page = ComposeElementsPage
@Test
fun clickTest(){
page.likesCounter.withMetaInfo("likesCounter")
.click()
.assertTextContains(option = TextContainsOption(substring = true), expected = "= 1")
}
@Test
fun longClickTest(){
page.longAndDoubleClickButton.withName("longAndDoubleClickButton").longClick()
page.status.assertTextEquals(ActionsStatus.LongClicked.name)
}
@Test
fun doubleClick_doubleClickable() {
page.longAndDoubleClickButton.withName("longAndDoubleClickButton").doubleClick()
page.status.assertTextEquals(ActionsStatus.DoubleClicked.name)
}
@Test
fun swipeDownTest(){
page.swipeableNode.withName("swipeableNode").swipeDown()
page.status.assertTextEquals(ActionsStatus.SwipeDown.name)
}
@Test
fun swipeUp() {
page.swipeableNode.withName("swipeableNode").swipeUp()
page.status.assertTextEquals(ActionsStatus.SwipeUp.name)
}
@Test
fun swipeRight() {
page.swipeableNode.withName("swipeableNode").swipeRight()
page.status.assertTextEquals(ActionsStatus.SwipeRight.name)
}
@Test
fun swipeLeft() {
page.swipeableNode.withName("swipeableNode").swipeLeft()
page.status.assertTextEquals(ActionsStatus.SwipeLeft.name)
}
@Test
fun swipe_option() {
page.swipeableNode.withName("swipeableNode").swipeLeft(ComposeSwipeOption(durationMs = 1000L))
page.status.assertTextEquals(ActionsStatus.SwipeLeft.name)
}
@Test
fun swipe_general() {
page.swipeableNode.withName("Swipeable Node").swipe(ComposeSwipeOption(
startXOffset = 0.1f,
startYOffset = 0.1f,
endXOffset = 0.9f,
endYOffset = 0.1f,
durationMs = 1000L
))
page.status.assertTextEquals(ActionsStatus.SwipeRight.name)
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/TreeTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.printToString
import com.atiurin.sampleapp.activity.ComposeElementsActivity
import com.atiurin.sampleapp.pages.ComposeElementsPage
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.allure.attachment.AttachUtil
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.file.MimeType
import com.atiurin.ultron.log.UltronLog
import com.atiurin.ultron.utils.createCacheFile
import org.junit.Test
class TreeTest : BaseTest() {
val page = ComposeElementsPage
val composeRule = createSimpleUltronComposeRule()
init {
ruleSequence.add(composeRule)
}
@Test
fun generateSemanticsTreeTest(){
val node = composeRule.onRoot(useUnmergedTree = true).printToString()
val file = createCacheFile("tree_", ".log")
file.writeText(node)
val fileName = AttachUtil.attachFile(file, MimeType.PLAIN_TEXT)
UltronLog.error(node)
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/UltronComposeUiBlockTest.kt
================================================
package com.atiurin.sampleapp.tests.compose
import androidx.compose.ui.test.hasTestTag
import com.atiurin.sampleapp.activity.ComposeElementsActivity
import com.atiurin.sampleapp.activity.ComposeElementsActivity.Constants.contactBlock1Tag
import com.atiurin.sampleapp.data.repositories.CONTACTS
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.pages.uiblock.ComposeListUiBlock.Companion.listBlockDesc
import com.atiurin.sampleapp.pages.uiblock.ComposeUiBlockScreen
import com.atiurin.sampleapp.pages.uiblock.ContactUiBlockWithDesc
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.sampleapp.tests.espresso.descriptionPrefix
import com.atiurin.sampleapp.tests.espresso.сhildNameDesc
import com.atiurin.ultron.core.common.assertion.softAssertion
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.extensions.assertIsDisplayed
import com.atiurin.ultron.extensions.assertTextContains
import com.atiurin.ultron.extensions.withUseUnmergedTree
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
class UltronComposeUiBlockTest : BaseTest() {
@get:Rule
val composeRule = createSimpleUltronComposeRule()
@Test
fun noUniqueElementTest() {
ComposeUiBlockScreen {
contactBlock1.blockMatcher.withUseUnmergedTree(true).printToLog("tree")
contactBlock1.statusDeepSearchText.assertIsDisplayed()
AssertUtils.assertException { contactBlock1.nameWithoutDeepSearch.withTimeout(100).assertIsDisplayed() }
contactBlock2.name.assertIsDisplayed()
}
}
@Test
fun uiBlockInBlock() {
ComposeUiBlockScreen {
contactListBlock.blockMatcher.withUseUnmergedTree(true).printToLog("tree")
contactListBlock.itemWithoutDesc.statusDeepSearchText.assertIsDisplayed()
contactListBlock.itemWithoutDesc.uiBlock.assertIsDisplayed()
contactListBlock.item1BlockWithDesc.name.assertIsDisplayed()
AssertUtils.assertException { contactListBlock.itemWithoutDesc.nameWithoutDeepSearch.withTimeout(100).assertIsDisplayed() }
}
}
@Test
fun childElementDescription() {
val blockDesc = "Parent_Name"
val expectedChildName = "${ContactUiBlockWithDesc.сhildNameDesc} $blockDesc"
ContactUiBlockWithDesc(hasTestTag(contactBlock1Tag), blockDesc).name.assertIsDisplayed().withResultHandler {
Assert.assertEquals(expectedChildName, it.operation.elementInfo.name)
}.withTimeout(100).assertTextContains("Invalid text")
}
@Test
fun childBlockDescriptionTest() {
val expectedItem1Description = "1 $descriptionPrefix $listBlockDesc"
val expectedItem2Description = "2 $descriptionPrefix $listBlockDesc"
val expectedChildNameDescInBlock1 = "$сhildNameDesc $expectedItem1Description"
val expectedChildNameDescInBlock2 = "$сhildNameDesc $expectedItem2Description"
ComposeUiBlockScreen {
softAssertion {
contactListBlock.item1BlockWithDesc.uiBlock.withTimeout(100).withResultHandler {
Assert.assertEquals(expectedItem1Description, it.operation.elementInfo.name)
}.assertTextEquals("Invalid")
contactListBlock.item1BlockWithDesc.name.withTimeout(100).withResultHandler {
Assert.assertEquals(expectedChildNameDescInBlock1, it.operation.elementInfo.name)
}.assertTextEquals("Invalid")
contactListBlock.item2BlockFactory.name.withTimeout(100).withResultHandler {
Assert.assertEquals(expectedChildNameDescInBlock2, it.operation.elementInfo.name)
}.assertTextEquals("Invalid")
}
}
}
@Test
fun properSearchOfElementsTest(){
ComposeUiBlockScreen {
softAssertion {
contactBlock1.statusDeepSearchText.assertTextContains(CONTACTS[0].status)
contactListBlock.item1BlockWithDesc.name.assertTextContains(CONTACTS[0].name)
contactListBlock.item1BlockWithDesc.status.assertTextContains(CONTACTS[0].status)
contactListBlock.item2BlockFactory.name.assertTextContains(CONTACTS[1].name)
contactListBlock.item2BlockFactory.status.assertTextContains(CONTACTS[1].status)
}
}
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/elements/DataPickerTest.kt
================================================
package com.atiurin.sampleapp.tests.compose.elements
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.performCustomAccessibilityActionWithLabel
import com.atiurin.sampleapp.activity.ComposeRouterActivity
import com.atiurin.sampleapp.compose.DatePickerTestData
import com.atiurin.sampleapp.compose.DatePickerTestTags
import com.atiurin.sampleapp.compose.DatePickerTestTags.SetDatePickerTimeCustomActionLabel
import com.atiurin.sampleapp.compose.screen.NavigationTestTags
import com.atiurin.sampleapp.framework.utils.TimeUtils
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.sampleapp.utils.convertMillisToDate
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.core.compose.nodeinteraction.UltronComposeSemanticsNodeInteraction
import com.atiurin.ultron.core.compose.operation.ComposeOperationType
import com.atiurin.ultron.core.compose.operation.UltronComposeOperationParams
import com.atiurin.ultron.extensions.assertTextContains
import com.atiurin.ultron.extensions.click
import com.atiurin.ultron.testlifecycle.setupteardown.SetUpRule
import org.junit.Test
import java.util.concurrent.TimeUnit
class DataPickerTest : BaseTest() {
private val composeRule = createSimpleUltronComposeRule()
private val navigateRule = SetUpRule().add { hasTestTag(NavigationTestTags.DatePicker).click() }
init {
ruleSequence.add(composeRule, navigateRule)
}
/**
* [convertMillisToDate] is defined in app to show the date, see [com.atiurin.sampleapp.compose.DatePickerKt.DatePickerDocked]
* [TimeUtils.getTimestampStartOfDay] is used cause DatePicker return a start of the selected date timestamp
*/
@Test
fun selectDateTest(){
hasTestTag(DatePickerTestTags.DockedIconButton).click()
val time = TimeUtils.getTimestampStartOfDay() + TimeUnit.DAYS.toMillis(120)
hasTestTag(DatePickerTestTags.DataPicker).setDatePickerTime(time)
hasTestTag(DatePickerTestTags.SelectedDateValue).assertTextContains(convertMillisToDate(time))
}
}
// Make this action native for Ultron
@OptIn(ExperimentalTestApi::class)
fun UltronComposeSemanticsNodeInteraction.setDatePickerTime(timeMs: Long) = perform(
UltronComposeOperationParams(
operationName = "SetDatePickerTime '${TimeUtils.formatTimestamp(timeMs)}' for '${elementInfo.name}'",
operationDescription = "Compose SetDatePickerTime '${TimeUtils.formatTimestamp(timeMs)}' for '${elementInfo.name}' during $timeoutMs ms",
operationType = ComposeOperationType.CUSTOM
)
) {
DatePickerTestData.time = timeMs
semanticsNodeInteraction.performCustomAccessibilityActionWithLabel(SetDatePickerTimeCustomActionLabel)
}
fun SemanticsMatcher.setDatePickerTime(timeMs: Long) = UltronComposeSemanticsNodeInteraction(this).setDatePickerTime(timeMs)
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/espresso/CustomClicksTest.kt
================================================
package com.atiurin.sampleapp.tests.espresso
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.matcher.ViewMatchers.withId
import com.atiurin.sampleapp.R
import com.atiurin.sampleapp.activity.CustomClicksActivity
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.extensions.*
import com.atiurin.ultron.testlifecycle.setupteardown.SetUpRule
import org.junit.Test
class CustomClicksTest : BaseTest() {
private val startActivity = SetUpRule().add {
ActivityScenario.launch(CustomClicksActivity::class.java)
}
init {
ruleSequence.addLast(startActivity)
}
@Test
fun clickTopLeft() {
withId(R.id.imageView).clickTopLeft(offsetX = 30, offsetY = 30)
withId(R.id.rB_top_left).isChecked()
}
@Test
fun clickTopCenter() {
withId(R.id.imageView).clickTopCenter(offsetY = 30)
withId(R.id.rB_top_center).isChecked()
}
@Test
fun clickTopRight() {
withId(R.id.imageView).clickTopRight(offsetX = -30, offsetY = 30)
withId(R.id.rB_top_right).isChecked()
}
@Test
fun clickCenterRight() {
withId(R.id.imageView).clickCenterRight(offsetX = -30)
withId(R.id.rB_center_right).isChecked()
}
@Test
fun clickBottomRight() {
withId(R.id.imageView).clickBottomRight(offsetX = -30, offsetY = -30)
withId(R.id.rB_bottom_right).isChecked()
}
@Test
fun clickBottomCenter() {
withId(R.id.imageView).clickBottomCenter(offsetY = -30)
withId(R.id.rB_bottom_center).isChecked()
}
@Test
fun clickBottomLeft() {
withId(R.id.imageView).clickBottomLeft(offsetX = 30, offsetY = -30)
withId(R.id.rB_bottom_left).isChecked()
}
@Test
fun clickCenterLeft() {
withId(R.id.imageView).clickCenterLeft(offsetX = 30)
withId(R.id.rB_center_left).isChecked()
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/espresso/CustomMatchersTest.kt
================================================
package com.atiurin.sampleapp.tests.espresso
import androidx.test.espresso.matcher.ViewMatchers.withId
import com.atiurin.sampleapp.R
import com.atiurin.sampleapp.activity.MainActivity
import com.atiurin.sampleapp.data.repositories.CONTACTS
import com.atiurin.sampleapp.data.repositories.ContactRepositoty
import com.atiurin.sampleapp.pages.ChatPage
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.custom.espresso.matcher.first
import com.atiurin.ultron.custom.espresso.matcher.hierarchyNumber
import com.atiurin.ultron.extensions.click
import com.atiurin.ultron.extensions.hasText
import com.atiurin.ultron.testlifecycle.activity.UltronActivityRule
import org.junit.Test
class CustomMatchersTest : BaseTest() {
private val activityTestRule = UltronActivityRule(MainActivity::class.java)
init {
ruleSequence.addLast(activityTestRule)
}
@Test
fun actionOnFirstMatchedView(){
withId(R.id.tv_name).first().click()
ChatPage.assertToolbarTitle(ContactRepositoty.getFirst().name)
}
@Test
fun assertionOnFirstMatchedView(){
withId(R.id.tv_name).first().hasText(ContactRepositoty.getFirst().name)
}
@Test
fun actionOnHierarchyNumberedItem(){
withId(R.id.tv_name).hierarchyNumber(1).click()
ChatPage.assertToolbarTitle(CONTACTS[1].name)
}
@Test
fun assertionOnHierarchyNumberedItem(){
withId(R.id.tv_name).hierarchyNumber(1).hasText(CONTACTS[1].name)
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/espresso/DemoEspressoTest.kt
================================================
package com.atiurin.sampleapp.tests.espresso
import com.atiurin.sampleapp.activity.MainActivity
import com.atiurin.sampleapp.pages.ChatPage
import com.atiurin.sampleapp.pages.FriendsListPage
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.core.espresso.UltronEspresso
import com.atiurin.ultron.extensions.doesNotExist
import com.atiurin.ultron.extensions.isDisplayed
import com.atiurin.ultron.testlifecycle.activity.UltronActivityRule
import org.junit.Test
class DemoEspressoTest : BaseTest() {
private val activityTestRule = UltronActivityRule(MainActivity::class.java)
init {
ruleSequence.addLast(activityTestRule)
}
@Test
fun friendsItemCheck() {
FriendsListPage {
assertName("Janice")
assertStatus("Janice", "Oh. My. God")
}
}
@Test
fun sendMessage() {
FriendsListPage.openChat("Chandler Bing")
ChatPage
.clearHistory()
.sendMessage("test message")
}
@Test
fun checkMessagesPositionsInChat() {
val firstMessage = "first message"
val secondMessage = "second message"
FriendsListPage.openChat("Janice")
ChatPage {
clearHistory()
sendMessage(firstMessage)
sendMessage(secondMessage)
assertMessageTextAtPosition(0, firstMessage)
}
}
@Test
fun pressBackTest(){
FriendsListPage.openChat("Chandler Bing")
ChatPage.assertPageDisplayed()
UltronEspresso.pressBack()
FriendsListPage.assertPageDisplayed()
}
@Test
fun openContextualActionModeOverflowMenuTest(){
FriendsListPage.openChat("Chandler Bing")
ChatPage.clearHistoryBtn.doesNotExist()
UltronEspresso.openContextualActionModeOverflowMenu()
ChatPage.clearHistoryBtn.isDisplayed()
}
@Test
fun openActionBarOverflowOrOptionsMenuTest(){
FriendsListPage.openChat("Chandler Bing")
ChatPage.clearHistoryBtn.doesNotExist()
UltronEspresso.openActionBarOverflowOrOptionsMenu()
ChatPage.clearHistoryBtn.isDisplayed()
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/espresso/RecyclerPerfTest.kt
================================================
package com.atiurin.sampleapp.tests.espresso
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.atiurin.sampleapp.MyApplication
import com.atiurin.sampleapp.R
import com.atiurin.sampleapp.activity.MainActivity
import com.atiurin.sampleapp.data.repositories.ContactRepositoty
import com.atiurin.sampleapp.framework.Log
import com.atiurin.sampleapp.pages.FriendsListPage
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.extensions.isDisplayed
import com.atiurin.ultron.testlifecycle.activity.UltronActivityRule
import com.atiurin.ultron.testlifecycle.setupteardown.SetUpRule
import org.junit.Test
class RecyclerPerfTest : BaseTest() {
private val activityTestRule = UltronActivityRule(MainActivity::class.java)
private val timeoutRule = SetUpRule().add {
MyApplication.CONTACTS_LOADING_TIMEOUT_MS = 0L
}
init {
ruleSequence.addLast(timeoutRule, activityTestRule)
}
@Test
fun test2() {
Log.time("Scroll+Click") {
(0..100).forEach { _ ->
onView(withId(R.id.recycler_friends))
.perform(
RecyclerViewActions
.scrollTo(
hasDescendant(withText(ContactRepositoty.getLast().name)),
)
)
withText(ContactRepositoty.getLast().name).isDisplayed()
onView(withId(R.id.recycler_friends))
.perform(
RecyclerViewActions
.scrollTo(
hasDescendant(withText(ContactRepositoty.getFirst().name)),
)
)
withText(ContactRepositoty.getFirst().name).isDisplayed()
}
}
}
@Test
fun recyclerViewV1PerfTest() {
Log.time("FriendsPageClick") {
(0..100).forEach { _ ->
FriendsListPage.getListItem(ContactRepositoty.getLast().name).name.isDisplayed()
FriendsListPage.getListItem(ContactRepositoty.getFirst().name).name.isDisplayed()
}
}
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/espresso/RecyclerViewTest.kt
================================================
package com.atiurin.sampleapp.tests.espresso
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.atiurin.sampleapp.MyApplication
import com.atiurin.sampleapp.R
import com.atiurin.sampleapp.activity.MainActivity
import com.atiurin.sampleapp.data.repositories.CONTACTS
import com.atiurin.sampleapp.data.repositories.ContactRepositoty
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.pages.ChatPage
import com.atiurin.sampleapp.pages.FriendsListPage
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.core.espresso.recyclerview.withRecyclerView
import com.atiurin.ultron.extensions.withAssertion
import com.atiurin.ultron.extensions.withName
import com.atiurin.ultron.extensions.withTimeout
import com.atiurin.ultron.testlifecycle.activity.UltronActivityRule
import com.atiurin.ultron.testlifecycle.setupteardown.SetUp
import com.atiurin.ultron.testlifecycle.setupteardown.SetUpRule
import com.atiurin.ultron.testlifecycle.setupteardown.TearDown
import com.atiurin.ultron.testlifecycle.setupteardown.TearDownRule
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.containsString
import org.junit.Assert
import org.junit.Test
class RecyclerViewTest : BaseTest() {
companion object {
const val CUSTOM_TIMEOUT = "CUSTOM_TIMEOUT"
val notExistItemMatcher = hasDescendant(withText("zxcbmzxmbc"))
}
private val setUpRule = SetUpRule().add(CUSTOM_TIMEOUT) {
MyApplication.CONTACTS_LOADING_TIMEOUT_MS = 6_000L
}
private val tearDownRule = TearDownRule().add(CUSTOM_TIMEOUT) {
MyApplication.CONTACTS_LOADING_TIMEOUT_MS = 500L
}
init {
ruleSequence.add(setUpRule, tearDownRule).addLast(UltronActivityRule(MainActivity::class.java))
}
val page = FriendsListPage
@Test
fun childTest() {
withRecyclerView(R.id.recycler_friends).withName("Friends list").item(0).getChild(withId(R.id.tv_status).withName("Status")).hasText(ContactRepositoty.getFirst().status)
}
@Test
fun testDisplayedItemPositions() {
for (index in 0..3) {
page.recycler.item(index).assertMatches(hasDescendant(withText(CONTACTS[index].name)))
.assertMatches(hasDescendant(withText(CONTACTS[index].status))).isDisplayed()
}
}
@Test
fun getRecyclerViewTest() {
val view = page.recycler.getRecyclerViewList()
Assert.assertNotNull(view)
Assert.assertEquals(Visibility.VISIBLE.value, view.visibility)
}
@Test
fun scrollToItemTest() {
val contact = CONTACTS[CONTACTS.size - 1]
val item = page.getListItem(contact.name)
item.isDisplayed()
item.name.hasText(contact.name)
item.status.hasText(contact.status)
}
@Test
fun wrongChild() {
val contact = CONTACTS[CONTACTS.size - 1]
val wrongContact = CONTACTS[CONTACTS.size - 2]
val item = page.getListItem(contact.name)
item.isDisplayed()
AssertUtils.assertException { item.name.withTimeout(100).hasText(wrongContact.name) }
}
@Test
fun recyclerViewItemClassTest() {
val contact = CONTACTS[1]
with(page.getListItem(contact.name)) {
this.isDisplayed().isClickable()
this.name.isDisplayed().isEnabled().hasText(contact.name)
this.status.isDisplayed().isEnabled().hasText(contact.status)
}
}
@Test
fun scrollToLastItem() {
withRecyclerView(R.id.recycler_friends).item(CONTACTS.size - 1).isDisplayed()
}
@Test
fun scrollToLastWithMatcher() {
withRecyclerView(R.id.recycler_friends).item(hasDescendant(withText("Friend14"))).isDisplayed()
}
@Test
fun getNotExistedRecyclerItemWithPosition() {
AssertUtils.assertException {
page.recycler.item(100).withTimeout(100).isDisplayed()
}
}
@Test
fun assertListSize() {
page.recycler.assertSize(CONTACTS.size)
}
@Test
fun recyclerView_notExist() {
AssertUtils.assertException {
withRecyclerView(withText("Not existed recycler")).withTimeout(100).isDisplayed()
}
}
@Test
fun item_notExist() {
AssertUtils.assertException {
page.recycler.item(withText("Not existed item"), false).withTimeout(100).isDisplayed()
}
}
@Test
fun item_notExist_executionTime() {
val timeout = 5_000L
AssertUtils.assertExecTimeMoreThen(timeout){
AssertUtils.assertException {
runCatching { page.recycler.withTimeout(timeout).item(withText("Not existed item")).isDisplayed() }
}
}
}
@Test
@SetUp(CUSTOM_TIMEOUT)
@TearDown(CUSTOM_TIMEOUT)
fun defaultTimeoutOnItemWaiting() {
AssertUtils.assertException { page.recycler.item(10).isDisplayed() }
}
@Test
@SetUp(CUSTOM_TIMEOUT)
@TearDown(CUSTOM_TIMEOUT)
fun customTimeoutOnItemWaiting() {
withRecyclerView(R.id.recycler_friends, 8000).item(10).isDisplayed()
}
@Test
@SetUp(CUSTOM_TIMEOUT)
@TearDown(CUSTOM_TIMEOUT)
fun item_autoScroll_False_item_NotLoaded() {
AssertUtils.assertException { page.recycler.item(10, false).isDisplayed() }
}
@Test
@SetUp(CUSTOM_TIMEOUT)
@TearDown(CUSTOM_TIMEOUT)
fun item_autoScroll_False_scroll_Force() {
withRecyclerView(R.id.recycler_friends, 8_000).item(10, false).scrollToItem().isDisplayed()
}
@Test
fun itemMatcher_autoScroll_false_itemNotDisplayed() {
AssertUtils.assertException {
page.recycler.item(
hasDescendant(withText("Friend14")), false
).isDisplayed()
}
}
@Test
fun itemMatcher_autoScroll_false_scroll_force() {
page.recycler.item(hasDescendant(withText("Friend14")), false).scrollToItem().isDisplayed()
}
@Test
@SetUp(CUSTOM_TIMEOUT)
@TearDown(CUSTOM_TIMEOUT)
fun itemMatcher_autoScroll_false() {
AssertUtils.assertException {
page.recycler.item(
hasDescendant(withText("Friend14")), false
).scrollToItem().isDisplayed()
}
}
@Test
@SetUp(CUSTOM_TIMEOUT)
@TearDown(CUSTOM_TIMEOUT)
fun itemMatcher_autoScroll_true_custom_timeout() {
withRecyclerView(R.id.recycler_friends, 10_000).item(hasDescendant(withText("Friend14"))).isDisplayed()
}
@Test
fun getViewHolder() {
val position = CONTACTS.size - 1
Assert.assertEquals(
position,
page.recycler.item(hasDescendant(withText(CONTACTS[position].name)))
.getViewHolder()
?.layoutPosition
)
}
@Test
fun transferFromGenericToSubclass() {
val position = 5
page.recycler.getItem(position)
.status.hasText(CONTACTS[position].status).isDisplayed()
}
@Test
fun getViewHolderList() {
page.recycler.waitItemsLoaded()
Assert.assertTrue(page.recycler.getViewHolderList(hasDescendant(withId(R.id.tv_name))).isNotEmpty())
}
@Test
fun waitLoaded_ofAlreadyLoadedList() {
page.recycler.waitItemsLoaded()
page.recycler.waitItemsLoaded()
}
@Test
fun waitLoaded_allItemsLoaded() {
val count = page.recycler.waitItemsLoaded().getSize()
Assert.assertEquals(CONTACTS.size, count)
}
@Test
fun getLastItem() {
page.recycler.lastItem().isDisplayed().click()
}
@Test
fun getLastItemWithCustomType() {
page.recycler.getLastItem().name.hasText(ContactRepositoty.getLast().name)
}
@Test
fun perfScroll() {
page.recycler.apply {
for (i in 0..10) {
lastItem().isDisplayed()
firstItem().isDisplayed()
}
}
}
@Test
fun getViewHolderAtPosition_outOfVisibleList() {
Assert.assertNull(page.recycler.waitItemsLoaded().getViewHolderAtPosition(15))
}
@Test
fun getViewHolderAtPosition_inVisibleList() {
Assert.assertNotNull(page.recycler.waitItemsLoaded().getViewHolderAtPosition(2))
}
@Test
fun getItemsAdapterPositionList() {
page.recycler.waitItemsLoaded()
val matcher = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString("Friend"))))
Assert.assertEquals(0, page.recycler.getViewHolderList(matcher).size)
Assert.assertTrue(page.recycler.getItemsAdapterPositionList(matcher).isNotEmpty())
}
@Test
fun firstItemMatched_existItem() {
val pattern = "Friend"
val expectedContacts = CONTACTS.filter { it.name.contains(pattern) }
val matcher = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(pattern))))
page.recycler.firstItemMatched(matcher).isDisplayed().click()
ChatPage.assertToolbarTitle(expectedContacts.first().name)
}
@Test
fun itemMatched_existItem() {
val pattern = "Friend"
val expectedContacts = CONTACTS.filter { it.name.contains(pattern) }
val matcher = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(pattern))))
page.recycler.itemMatched(matcher, 1).isDisplayed().click()
ChatPage.assertToolbarTitle(expectedContacts[1].name)
}
@Test
fun itemMatched_notExistItem() {
val matcher = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString("Friend"))))
AssertUtils.assertException { page.recycler.withTimeout(1000).itemMatched(matcher, 99) }
}
@Test
fun lastItemMatched_existItem() {
val pattern = "Friend"
val expectedContacts = CONTACTS.filter { it.name.contains(pattern) }
val matcher = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(pattern))))
page.recycler.itemMatched(matcher, expectedContacts.lastIndex).isDisplayed().click()
ChatPage.assertToolbarTitle(expectedContacts.last().name)
}
@Test
fun getFirstItemMatched_existItem() {
val pattern = "Friend"
val expectedContact = CONTACTS.filter { it.name.contains(pattern) }.first()
val matcher = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(pattern))))
page.recycler.getFirstItemMatched(matcher).apply {
name.isDisplayed().hasText(expectedContact.name)
status.hasText(expectedContact.status)
click()
}
ChatPage.assertToolbarTitle(expectedContact.name)
}
@Test
fun getItemMatched_existItem() {
val pattern = "Friend"
val expectedContact = CONTACTS.filter { it.name.contains(pattern) }[1]
val matcher = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(pattern))))
page.recycler.getItemMatched(matcher, 1).apply {
name.isDisplayed().hasText(expectedContact.name)
status.hasText(expectedContact.status)
click()
}
ChatPage.assertToolbarTitle(expectedContact.name)
}
@Test
fun getItemMatched_notExistItem() {
val matcher = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString("Friend"))))
AssertUtils.assertException { page.recycler.withTimeout(1000).getItemMatched(matcher, 99) }
}
@Test
fun getLastItemMatched_existItem() {
val pattern = "Friend"
val expectedContacts = CONTACTS.filter { it.name.contains(pattern) }
val matcher = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(pattern))))
page.recycler.getItemMatched(matcher, expectedContacts.lastIndex).apply {
name.isDisplayed().hasText(expectedContacts.last().name)
status.hasText(expectedContacts.last().status)
click()
}
ChatPage.assertToolbarTitle(expectedContacts.last().name)
}
@Test
fun assertItemNotExist_notExistItem() {
page.recycler.assertItemNotExist(notExistItemMatcher, 2000)
}
@Test
fun assertItemNotExist_existItem() {
val matcher = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(CONTACTS.first().name))))
AssertUtils.assertException { page.recycler.assertItemNotExist(matcher, 2000) }
}
@Test
fun assertItemNotExistImmediately_notExistItem() {
page.recycler.assertItemNotExistImmediately(notExistItemMatcher, 2000)
}
@Test
fun assertItemNotExistImmediately_existItem() {
page.recycler.waitItemsLoaded()
val matcher = hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(CONTACTS.first().name))))
AssertUtils.assertException { page.recycler.assertItemNotExistImmediately(matcher, 2000) }
}
@Test
fun assertItemOutOfLimitNotFound() {
val rv = withRecyclerView(R.id.recycler_friends, itemSearchLimit = 2)
AssertUtils.assertException {
rv.withTimeout(2000L)
.item(hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(CONTACTS[10].name)))))
.click()
}
}
@Test
fun assertItemInLimitFound() {
val rv = withRecyclerView(R.id.recycler_friends, itemSearchLimit = 10)
rv.withTimeout(2000L)
.item(hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(CONTACTS[2].name)))))
.isDisplayed()
}
@Test
fun createHandlerFromUiTest() {
page.recycler.getItemAdapterPositionAtIndex(hasDescendant(allOf(withId(R.id.tv_name), withText(containsString(CONTACTS.last().name)))), 0)
}
//item+offset
@Test
fun item_scrollOffsetInItemCountRange_MatcherItem() {
val target = 5
val offset = 10
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.item(page.getItemMatcher(targetContact), scrollOffset = offset)
page.recycler.item(page.getItemMatcher(offsetContact), autoScroll = false).isDisplayed().click()
}
@Test
fun item_scrollOffsetInItemCountRangeBothAreVisible_MatcherItem() {
val target = 8
val offset = 2
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.item(page.getItemMatcher(targetContact), scrollOffset = offset).isDisplayed()
page.recycler.item(page.getItemMatcher(offsetContact), autoScroll = false).isDisplayed()
}
@Test
fun item_scrollOffsetOutOfItemCountRange_MatcherItem() {
val target = 5
val offset = CONTACTS.size
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS.last()
page.recycler.item(page.getItemMatcher(targetContact), scrollOffset = offset)
page.recycler.item(page.getItemMatcher(offsetContact), autoScroll = false).isDisplayed().click()
}
@Test
fun item_scrollOffsetLessThenZero_MatcherItem() {
val target = 8
val offset = -18
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS.first()
page.recycler.item(page.getItemMatcher(targetContact), scrollOffset = offset)
page.recycler.item(page.getItemMatcher(offsetContact), autoScroll = false).isDisplayed().click()
}
@Test
fun item_scrollOffsetInItemCountRange_positionItem() {
val target = 2
val offset = 12
val offsetContact = CONTACTS[target + offset]
page.recycler.item(target, scrollOffset = offset)
page.recycler.item(target + offset, autoScroll = false).isDisplayed()
page.recycler.getItem(target + offset, autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun item_scrollOffsetInItemCountRangeBothAreVisible_positionItem() {
val target = 8
val offset = 2
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.item(target, scrollOffset = offset).isDisplayed()
page.recycler.item(target + offset, autoScroll = false).isDisplayed()
page.recycler.getItem(target, autoScroll = false).name.hasText(targetContact.name).isDisplayed()
page.recycler.getItem(target + offset, autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun item_scrollOffsetOutOfItemCountRange_positionItem() {
val target = 5
val offset = CONTACTS.size
val offsetContact = CONTACTS.last()
page.recycler.item(target, scrollOffset = offset)
page.recycler.lastItem(autoScroll = false).isDisplayed()
page.recycler.getLastItem(autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun item_scrollOffsetLessThenZero_positionItem() {
val target = 10
val offset = -18
val offsetContact = CONTACTS.first()
page.recycler.item(target, scrollOffset = offset)
page.recycler.getFirstItem(autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
//itemMatched+offset
@Test
fun itemMatched_scrollOffsetInItemCountRange_MatcherItem() {
val target = 5
val offset = 10
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.itemMatched(page.getItemMatcher(targetContact), 0, scrollOffset = offset)
page.recycler.item(page.getItemMatcher(offsetContact), autoScroll = false).isDisplayed().click()
}
@Test
fun itemMatched_scrollOffsetInItemCountRangeBothAreVisible_MatcherItem() {
val target = 8
val offset = 2
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.itemMatched(page.getItemMatcher(targetContact), 0, scrollOffset = offset).isDisplayed()
page.recycler.item(page.getItemMatcher(offsetContact), autoScroll = false).isDisplayed()
}
@Test
fun itemMatched_scrollOffsetOutOfItemCountRange_MatcherItem() {
val target = 5
val offset = CONTACTS.size
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS.last()
page.recycler.itemMatched(page.getItemMatcher(targetContact), 0, scrollOffset = offset)
page.recycler.item(page.getItemMatcher(offsetContact), autoScroll = false).isDisplayed().click()
}
//firstItemMatched+offset
@Test
fun firstItemMatched_scrollOffsetInItemCountRange_MatcherItem() {
val target = 5
val offset = 10
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.firstItemMatched(page.getItemMatcher(targetContact), scrollOffset = offset)
page.recycler.item(page.getItemMatcher(offsetContact), autoScroll = false).isDisplayed().click()
}
//lastItemMatched+offset
@Test
fun lastItemMatched_scrollOffsetInItemCountRange_MatcherItem() {
val target = 5
val offset = 10
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.lastItemMatched(page.getItemMatcher(targetContact), scrollOffset = offset)
page.recycler.item(page.getItemMatcher(offsetContact), autoScroll = false).isDisplayed().click()
}
//getItem+offset
@Test
fun getItem_scrollOffsetInItemCountRange_MatcherItem() {
val target = 5
val offset = 10
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.getItem(page.getItemMatcher(targetContact), scrollOffset = offset)
page.recycler.getItem(page.getItemMatcher(offsetContact), autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun getItem_scrollOffsetInItemCountRangeBothAreVisible_MatcherItem() {
val target = 8
val offset = 2
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.getItem(page.getItemMatcher(targetContact), scrollOffset = offset)
page.recycler.getItem(page.getItemMatcher(offsetContact), autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun getItem_scrollOffsetOutOfItemCountRange_MatcherItem() {
val target = 5
val offset = CONTACTS.size
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS.last()
page.recycler.getItem(page.getItemMatcher(targetContact), scrollOffset = offset)
page.recycler.getItem(page.getItemMatcher(offsetContact), autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun getItem_scrollOffsetLessThenZero_MatcherItem() {
val target = 8
val offset = -18
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS.first()
page.recycler.getItem(page.getItemMatcher(targetContact), scrollOffset = offset)
page.recycler.getItem(page.getItemMatcher(offsetContact), autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun getItem_scrollOffsetInItemCountRange_positionItem() {
val target = 2
val offset = 12
val offsetContact = CONTACTS[target + offset]
page.recycler.getItem(target, scrollOffset = offset)
page.recycler.getItem(target + offset, autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun getItem_scrollOffsetInItemCountRangeBothAreVisible_positionItem() {
val target = 8
val offset = 2
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.getItem(target, scrollOffset = offset).name.hasText(targetContact.name).isDisplayed()
page.recycler.getItem(target + offset, autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun getItem_scrollOffsetOutOfItemCountRange_positionItem() {
val target = 5
val offset = CONTACTS.size
val offsetContact = CONTACTS.last()
page.recycler.getItem(target, scrollOffset = offset)
page.recycler.getLastItem(autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun getItem_scrollOffsetLessThenZero_positionItem() {
val target = 10
val offset = -18
val offsetContact = CONTACTS.first()
page.recycler.getItem(target, scrollOffset = offset)
page.recycler.getFirstItem(autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
//getItemMatched+offset
@Test
fun getItemMatched_scrollOffsetInItemCountRange_MatcherItem() {
val target = 5
val offset = 10
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.getItemMatched(page.getItemMatcher(targetContact), 0, scrollOffset = offset)
page.recycler.getItem(page.getItemMatcher(offsetContact), autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun getItemMatched_scrollOffsetInItemCountRangeBothAreVisible_MatcherItem() {
val target = 8
val offset = 2
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.getItemMatched(page.getItemMatcher(targetContact), 0, scrollOffset = offset)
page.recycler.getItem(page.getItemMatcher(offsetContact), autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun getItemMatched_scrollOffsetOutOfItemCountRange_MatcherItem() {
val target = 5
val offset = CONTACTS.size
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS.last()
page.recycler.getItemMatched(page.getItemMatcher(targetContact), 0, scrollOffset = offset)
page.recycler.getItem(page.getItemMatcher(offsetContact), autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
//getFirstItemMatched+offset
@Test
fun getFirstItemMatched_scrollOffsetInItemCountRange_MatcherItem() {
val target = 5
val offset = 10
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.getFirstItemMatched(page.getItemMatcher(targetContact), scrollOffset = offset)
page.recycler.getItem(page.getItemMatcher(offsetContact), autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
//getLastItemMatched+offset
@Test
fun getLastItemMatched_scrollOffsetInItemCountRange_MatcherItem() {
val target = 5
val offset = 10
val targetContact = CONTACTS[target]
val offsetContact = CONTACTS[target + offset]
page.recycler.getLastItemMatched(page.getItemMatcher(targetContact), scrollOffset = offset)
page.recycler.getItem(page.getItemMatcher(offsetContact), autoScroll = false).name.hasText(offsetContact.name).isDisplayed()
}
@Test
fun validItemCustomAssertion() {
val contact = CONTACTS.first()
page.recycler.firstItem().withAssertion("Toolbar title = ${contact.name}") {
ChatPage.assertToolbarTitle(contact.name)
}.click()
}
@Test
fun invalidItemCustomAssertion() {
AssertUtils.assertException {
val invalidExpectedName = "InvalidTitle"
page.recycler.firstItem().withTimeout(3000).withAssertion("Toolbar title = $invalidExpectedName") {
ChatPage.assertToolbarTitle(invalidExpectedName)
}.click()
}
}
@Test
fun swipeUntil() {
withId(R.id.recycler_friends).withAssertion(isListened = true) {
withText(CONTACTS.last().name).withTimeout(200).isDisplayed()
}.swipeUp()
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/espresso/UltronActivityRuleTest.kt
================================================
package com.atiurin.sampleapp.tests.espresso
import android.content.Intent
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.platform.app.InstrumentationRegistry
import com.atiurin.sampleapp.activity.BusyActivity
import com.atiurin.sampleapp.activity.MainActivity
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.core.compose.createSimpleUltronComposeRule
import com.atiurin.ultron.core.compose.createUltronComposeRule
import com.atiurin.ultron.testlifecycle.activity.UltronActivityRule
import org.junit.Test
import org.junit.rules.Timeout
import java.util.concurrent.TimeUnit
class UltronActivityRuleTest: BaseTest() {
//private val activityTestRule = ActivityScenarioRule(MainActivity::class.java)
//private val activityTestRule = UltronActivityRule(MainActivity::class.java)
//private val activityTestRule = createUltronComposeRule()
private val activityTestRule = createSimpleUltronComposeRule()
private val timeoutRule: Timeout = Timeout
.builder()
.withTimeout(100, TimeUnit.SECONDS)
.withLookingForStuckThread(true)
.build()
init {
ruleSequence.add(timeoutRule, activityTestRule)
}
@Test
fun appNotIdle() {
val intent = Intent(
InstrumentationRegistry.getInstrumentation().targetContext,
BusyActivity::class.java
)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
InstrumentationRegistry.getInstrumentation().targetContext.startActivity(intent)
assert(true)
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/espresso/UltronEspressoConfigTest.kt
================================================
package com.atiurin.sampleapp.tests.espresso
import androidx.test.espresso.matcher.ViewMatchers
import com.atiurin.sampleapp.framework.DummyMetaObject
import com.atiurin.sampleapp.framework.Log
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.pages.UiElementsPage
import com.atiurin.sampleapp.tests.UiElementsTest
import com.atiurin.ultron.core.common.resultanalyzer.UltronDefaultOperationResultAnalyzer
import com.atiurin.ultron.core.config.UltronCommonConfig
import com.atiurin.ultron.core.config.UltronConfig
import com.atiurin.ultron.core.espresso.EspressoOperationResult
import com.atiurin.ultron.core.espresso.UltronEspressoOperation
import com.atiurin.ultron.exceptions.UltronException
import com.atiurin.ultron.extensions.click
import com.atiurin.ultron.extensions.hasText
import com.atiurin.ultron.extensions.isDisplayed
import com.atiurin.ultron.extensions.isSuccess
import com.atiurin.ultron.extensions.withAssertion
import com.atiurin.ultron.extensions.withName
import com.atiurin.ultron.extensions.withResultHandler
import com.atiurin.ultron.extensions.withTimeout
import com.atiurin.ultron.testlifecycle.setupteardown.SetUp
import com.atiurin.ultron.testlifecycle.setupteardown.SetUpRule
import com.atiurin.ultron.testlifecycle.setupteardown.TearDown
import com.atiurin.ultron.testlifecycle.setupteardown.TearDownRule
import org.junit.Assert
import org.junit.Test
import kotlin.system.measureTimeMillis
class UltronEspressoConfigTest : UiElementsTest() {
val page = UiElementsPage
companion object {
const val SET_CUSTOM_RESULT_ANALYZER = "SET_CUSTOM_RESULT_ANALYZER"
const val SET_DEFAULT_CONFIG = "SET_DEFAULT_CONFIG"
const val SET_CUSTOM_ASSERTIONS_TIMEOUT = "SET_ASSERTIONS_TIMEOUT"
const val SET_CUSTOM_ACTIONS_TIMEOUT = "SET_ACTIONS_TIMEOUT"
const val SET_DEFAULT_TIMEOUT = "DROP_DEFAULT_TIMEOUT"
const val MODIFIED_OPERATIONS_TIMEOUT = 7_000L
}
val setUpRule = SetUpRule()
.add(SET_CUSTOM_RESULT_ANALYZER) {
UltronConfig.Espresso.setResultAnalyzer {
Log.debug("SET_CUSTOM_RESULT_ANALYZER ${it.success}")
if (it.success) throw UltronException("Special reversed analyzer exception on ${it.description}")
it.success
}
}.add(SET_CUSTOM_ASSERTIONS_TIMEOUT) {
UltronConfig.Espresso.ASSERTION_TIMEOUT =
MODIFIED_OPERATIONS_TIMEOUT
}
.add(SET_CUSTOM_ACTIONS_TIMEOUT) {
UltronConfig.Espresso.ACTION_TIMEOUT =
MODIFIED_OPERATIONS_TIMEOUT
}
private val tearDownRule = TearDownRule()
.add(SET_DEFAULT_CONFIG) {
UltronConfig.Espresso.resultAnalyzer = UltronDefaultOperationResultAnalyzer()
}
.add(SET_DEFAULT_TIMEOUT) {
UltronConfig.Espresso.ACTION_TIMEOUT = UltronCommonConfig.Defaults.OPERATION_TIMEOUT_MS
UltronConfig.Espresso.ASSERTION_TIMEOUT = UltronCommonConfig.Defaults.OPERATION_TIMEOUT_MS
}
init {
ruleSequence.add(setUpRule, tearDownRule)
}
@Test
@SetUp(SET_CUSTOM_RESULT_ANALYZER)
@TearDown(SET_DEFAULT_CONFIG)
fun resultAnalyzer_reversed_should_throw_on_success_action() {
AssertUtils.assertException { page.button.click() }
}
@Test
@SetUp(SET_CUSTOM_RESULT_ANALYZER)
@TearDown(SET_DEFAULT_CONFIG)
fun resultAnalyzer_reversed_should_NOT_throw_on_failed_action() {
page.notExistElement.withTimeout(100).click()
}
@Test
@SetUp(SET_CUSTOM_RESULT_ANALYZER)
@TearDown(SET_DEFAULT_CONFIG)
fun resultAnalyzer_reversed_should_throw_on_success_assertion() {
AssertUtils.assertException { page.button.isDisplayed() }
}
@Test
@SetUp(SET_CUSTOM_RESULT_ANALYZER)
@TearDown(SET_DEFAULT_CONFIG)
fun resultAnalyzer_reversed_should_NOT_throw_on_failed_assertion() {
page.notExistElement.withTimeout(100).isDisplayed()
}
//timeouts
@Test
fun withTimeout_action_default() {
val default = UltronCommonConfig.Defaults.OPERATION_TIMEOUT_MS
AssertUtils.assertExecTimeBetween(default, default + 5_000) { page.notExistElement.click() }
}
@Test
fun withTimeou_actiont_customValue() {
AssertUtils.assertExecTimeBetween(2_000, 4_500) {
page.notExistElement.withTimeout(2000).click()
}
}
@Test
@SetUp(SET_CUSTOM_ACTIONS_TIMEOUT)
@TearDown(
SET_DEFAULT_TIMEOUT
)
fun withTimeout_action_modifiedDefaultValue() {
AssertUtils.assertExecTimeBetween(
MODIFIED_OPERATIONS_TIMEOUT, MODIFIED_OPERATIONS_TIMEOUT + 2_000L
) { page.notExistElement.click() }
}
@Test
fun withTimeout_assertion_default() {
val default = UltronCommonConfig.Defaults.OPERATION_TIMEOUT_MS
AssertUtils.assertExecTimeBetween(
default,
default + 2_000
) { page.notExistElement.isDisplayed() }
}
@Test
fun withTimeout_assertion_customValue() {
AssertUtils.assertExecTimeBetween(2_000, 4_500) {
page.notExistElement.withTimeout(2000).isDisplayed()
}
}
@Test
@SetUp(SET_CUSTOM_ASSERTIONS_TIMEOUT)
@TearDown(
SET_DEFAULT_TIMEOUT
)
fun withTimeout_assertion_modifiedDefaultValue() {
AssertUtils.assertExecTimeBetween(
MODIFIED_OPERATIONS_TIMEOUT,
MODIFIED_OPERATIONS_TIMEOUT + 2_000L
) { page.notExistElement.isDisplayed() }
}
//resultHandler
@Test
fun withResultHandler_action_default_true() {
var result: EspressoOperationResult? = null
page.button.withResultHandler {
result = it
}.click()
Assert.assertNotNull(result)
Assert.assertTrue(result!!.success)
// Assert.assertTrue(result!!.exceptions.isEmpty())
Assert.assertFalse(result!!.operation.name.isNullOrEmpty())
Assert.assertFalse(result!!.operation.description.isNullOrEmpty())
Assert.assertEquals(UltronConfig.Espresso.ACTION_TIMEOUT, result!!.operation.timeoutMs)
}
@Test
fun withResultHandler_action_default_false() {
var result: EspressoOperationResult? = null
page.notExistElement.withTimeout(100).withResultHandler {
result = it
}.click()
Assert.assertNotNull(result)
Assert.assertFalse(result!!.success)
Assert.assertTrue(result!!.exceptions.isNotEmpty())
Assert.assertFalse(result!!.operation.name.isNullOrEmpty())
Assert.assertFalse(result!!.operation.description.isNullOrEmpty())
Assert.assertEquals(100, result!!.operation.timeoutMs)
}
@Test
fun customAssertionTest() {
val text = "some text"
val execTime = measureTimeMillis {
page.editTextContentDesc.withAssertion("demo name") {
page.editTextContentDesc.hasText(text)
}.replaceText(text)
}
Assert.assertTrue(execTime < UltronConfig.Espresso.ACTION_TIMEOUT)
}
@Test
fun withAssertion_failedAssertion() {
AssertUtils.assertException {
page.editTextContentDesc.withTimeout(1000).withAssertion {
ViewMatchers.withText("asd23213 12312").withTimeout(500).isDisplayed()
}.typeText("1")
}
}
@Test
fun withAssertion_failedAssertion_timeout() {
val operationTime = 1000L
val execTime = measureTimeMillis {
page.editTextContentDesc.isSuccess {
withTimeout(operationTime).withAssertion {
ViewMatchers.withText("asd23213 12312").withTimeout(100).isDisplayed()
}.typeText("1")
}
}
Assert.assertTrue(execTime > operationTime)
}
@Test
fun withName_inOperationProps_ultronInteraction() {
val name = "ElementName"
page.notExistElement.withTimeout(100).withName(name).withResultHandler { result ->
Assert.assertEquals(name, result.operation.elementInfo.name)
}.isDisplayed()
}
@Test
fun withName_inOperationProps_matcherExt() {
val name = "ElementName"
page.notExistElement.withName(name).withTimeout(100).withResultHandler { result ->
Assert.assertEquals(name, result.operation.elementInfo.name)
}.isDisplayed()
}
@Test
fun withName_inExceptionMessage() {
val name = "ElementNameToBeInException"
runCatching {
page.notExistElement.withTimeout(100).withName(name).isDisplayed()
}.onFailure { exception ->
Assert.assertTrue(exception.message!!.contains(name))
}
}
@Test
fun withMeta() {
val meta = DummyMetaObject("ElementMetaInfo")
page.notExistElement.withTimeout(100).withMetaInfo(meta).withResultHandler { result ->
Assert.assertEquals(meta, result.operation.elementInfo.meta)
}.isDisplayed()
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/espresso/UltronEspressoUiBlockTest.kt
================================================
package com.atiurin.sampleapp.tests.espresso
import android.view.View
import androidx.test.espresso.matcher.ViewMatchers.withId
import com.atiurin.sampleapp.R
import com.atiurin.sampleapp.activity.UiBlockActivity
import com.atiurin.sampleapp.data.repositories.CONTACTS
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.pages.uiblock.EspressoUiBlockScreen
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.core.common.assertion.softAssertion
import com.atiurin.ultron.core.espresso.UltronEspressoUiBlock
import com.atiurin.ultron.extensions.withName
import com.atiurin.ultron.extensions.withTimeout
import com.atiurin.ultron.testlifecycle.activity.UltronActivityRule
import org.hamcrest.Matcher
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
class UltronEspressoUiBlockTest : BaseTest() {
@get:Rule
val activityRule = UltronActivityRule(UiBlockActivity::class.java)
@Test
fun notUniqueUiElement_WithDeepSearch() {
EspressoUiBlockScreen {
contactItem1.name.isDisplayed()
contactItem1.deepSearchChild.withTimeout(100).isDisplayed()
}
}
@Test
fun notUniqueUiElement_WithoutDeepSearch() {
EspressoUiBlockScreen {
AssertUtils.assertException {
blockWithoutDeepSearch.deepSearchFalse.withTimeout(100).isDisplayed()
}
}
}
@Test
fun uiBlockInBlock() {
EspressoUiBlockScreen {
contactsListBlock.item1.name.isDisplayed().hasText(CONTACTS[0].name)
contactsListBlock.item1.status.isDisplayed().hasText(CONTACTS[0].status)
contactsListBlock.item2.name.isDisplayed().hasText(CONTACTS[1].name)
contactsListBlock.item2.status.isDisplayed().hasText(CONTACTS[1].status)
}
}
@Test
fun childElementDescription() {
val descriptionPrefix = "Item with parent"
val blockDesc = "Parent_Name"
val expectedChildName = "$descriptionPrefix $blockDesc"
class BlockDesc(blockMatcher: Matcher, blockDescription: String) : UltronEspressoUiBlock(blockMatcher, blockDescription) {
val name = child(withId(R.id.name)).withName("$descriptionPrefix $blockDescription")
}
BlockDesc(withId(R.id.contact_item_1), blockDesc).name.isDisplayed().withResultHandler {
Assert.assertEquals(expectedChildName, it.operation.elementInfo.name)
}.withTimeout(100).hasText("Invalid text")
}
@Test
fun childBlockDescriptionTest() {
val listBlockDesc = "ListBlock"
val expectedItem1Description = "1 $descriptionPrefix $listBlockDesc"
val expectedItem2Description = "2 $descriptionPrefix $listBlockDesc"
val expectedChildNameDescInBlock1 = "$сhildNameDesc $expectedItem1Description"
val expectedChildNameDescInBlock2 = "$сhildNameDesc $expectedItem2Description"
val listBlock = ListUiBlock(withId(R.id.contact_items), listBlockDesc)
softAssertion {
listBlock.item1.uiBlock.withTimeout(100).withResultHandler {
Assert.assertEquals(expectedItem1Description, it.operation.elementInfo.name)
}.hasText("Invalid")
listBlock.item1.name.withTimeout(100).withResultHandler {
Assert.assertEquals(expectedChildNameDescInBlock1, it.operation.elementInfo.name)
}.hasText("Invalid")
listBlock.item2.name.withTimeout(100).withResultHandler {
Assert.assertEquals(expectedChildNameDescInBlock2, it.operation.elementInfo.name)
}.hasText("Invalid")
}
}
}
class BlockDesc(blockMatcher: Matcher, blockDescription: String) : UltronEspressoUiBlock(blockMatcher, blockDescription) {
val name = child(withId(R.id.name)).withName("$сhildNameDesc $blockDescription")
}
class ListUiBlock(blockMatcher: Matcher, blockDescription: String) : UltronEspressoUiBlock(blockMatcher, blockDescription) {
val item1 = child(BlockDesc(withId(R.id.contact_item_1), "1 $descriptionPrefix $blockDescription"))
val item2 = child(
childMatcher = withId(R.id.contact_item_2),
uiBlockFactory = { updatedMatcher ->
BlockDesc(updatedMatcher, blockDescription = "2 $descriptionPrefix $blockDescription")
}
)
}
const val descriptionPrefix = "Item with parent"
const val сhildNameDesc = "NamE"
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/espresso/ViewInteractionActionsTest.kt
================================================
package com.atiurin.sampleapp.tests.espresso
import android.os.SystemClock
import android.view.KeyEvent
import androidx.test.espresso.action.EspressoKey
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.atiurin.sampleapp.R
import com.atiurin.sampleapp.framework.ultronext.appendText
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.framework.utils.TestDataUtils.getResourceString
import com.atiurin.sampleapp.pages.UiElementsPage
import com.atiurin.sampleapp.tests.UiElementsTest
import com.atiurin.ultron.core.common.assertion.softAssertion
import com.atiurin.ultron.core.common.assertion.verifySoftAssertions
import com.atiurin.ultron.core.config.UltronCommonConfig
import com.atiurin.ultron.core.config.UltronConfig
import com.atiurin.ultron.core.espresso.UltronEspresso
import com.atiurin.ultron.custom.espresso.action.getContentDescription
import com.atiurin.ultron.custom.espresso.action.getDrawable
import com.atiurin.ultron.custom.espresso.action.getText
import com.atiurin.ultron.custom.espresso.assertion.hasAnyDrawable
import com.atiurin.ultron.custom.espresso.assertion.hasDrawable
import com.atiurin.ultron.extensions.clearText
import com.atiurin.ultron.extensions.click
import com.atiurin.ultron.extensions.closeSoftKeyboard
import com.atiurin.ultron.extensions.doubleClick
import com.atiurin.ultron.extensions.isDisplayed
import com.atiurin.ultron.extensions.isSameAs
import com.atiurin.ultron.extensions.isSuccess
import com.atiurin.ultron.extensions.longClick
import com.atiurin.ultron.extensions.perform
import com.atiurin.ultron.extensions.replaceText
import com.atiurin.ultron.extensions.textContains
import com.atiurin.ultron.extensions.withTimeout
import com.atiurin.ultron.utils.getTargetString
import org.junit.Assert
import org.junit.Test
class ViewInteractionActionsTest : UiElementsTest() {
val page = UiElementsPage
@Test
fun isSuccess_notExistedElement_return_false() {
val startTime = SystemClock.elapsedRealtime()
val result = page.notExistElement.isSuccess { isDisplayed() }
val endTime = SystemClock.elapsedRealtime()
Assert.assertTrue(endTime - startTime >= UltronConfig.Espresso.ASSERTION_TIMEOUT)
Assert.assertFalse(result)
}
@Test
fun isSuccess_existedElement_return_true() {
Assert.assertTrue(page.button.isSuccess { isDisplayed() })
}
@Test
fun click_onClickable() {
page.button.click()
page.eventStatus.textContains(getTargetString(R.string.button_event_click))
}
@Test
fun click_notExisted() {
AssertUtils.assertException { page.notExistElement.withTimeout(100).click() }
}
@Test
fun longClick_onLongClickable() {
page.button.longClick()
page.eventStatus.textContains(getTargetString(R.string.button_event_long_click))
}
@Test
fun longClick_notExisted() {
AssertUtils.assertException { page.notExistElement.withTimeout(100).longClick() }
}
@Test
fun doubleClick_onClickable() {
page.button.doubleClick()
page.button.withTimeout(1000).isDisplayed()
var success = false
with(page.eventStatus) {
textContains(getResourceString(R.string.button_event_click))
success = isSuccess { withTimeout(3000).textContains("1") } || isSuccess {
withTimeout(2000).textContains("2")
}
}
Assert.assertTrue(success)
}
@Test
fun doubleClick_notExisted() {
AssertUtils.assertException { page.notExistElement.withTimeout(100).doubleClick() }
}
@Test
fun typeText_onEditable() {
val text1 = "begin"
val text2 = "simple text"
page.editTextContentDesc
.replaceText(text1)
.typeText(text2)
.hasText("$text1$text2")
}
@Test
fun typeText_onNotEditable() {
AssertUtils.assertException { page.eventStatus.withTimeout(100).typeText("simple text") }
}
@Test
fun typeText_notExisted() {
AssertUtils.assertException { page.notExistElement.withTimeout(100).typeText("asd") }
}
@Test
fun replaceText_onEditable() {
val text = "simple text"
page.editTextContentDesc.replaceText(text).hasText(text)
}
@Test
fun replaceText_notExisted() {
AssertUtils.assertException { page.notExistElement.withTimeout(100).replaceText("asd") }
}
@Test
fun clearText_onEditable() {
page.editTextContentDesc.clearText().hasText("")
}
@Test
fun clearText_notExisted() {
AssertUtils.assertException { page.notExistElement.withTimeout(100).clearText() }
}
@Test
fun pressKey_onEditable() {
val text = "simple text"
val expectedText = text.substring(0, text.length - 1)
page.editTextContentDesc
.replaceText(text)
.click()
.pressKey(KeyEvent.KEYCODE_DEL)
.hasText(expectedText)
}
@Test
fun pressKey_notExisted() {
AssertUtils.assertException {
page.notExistElement.withTimeout(100).pressKey(KeyEvent.KEYCODE_DEL)
}
}
@Test
fun pressEspressoKey_onEditable() {
val text = "simple text"
val expectedText = text.substring(0, text.length - 1)
page.editTextContentDesc
.replaceText(text)
.click()
.pressKey(EspressoKey.Builder().withKeyCode(KeyEvent.KEYCODE_DEL).build())
.hasText(expectedText)
}
@Test
fun pressEspressoKey_notExisted() {
AssertUtils.assertException {
page.notExistElement.withTimeout(100)
.pressKey(EspressoKey.Builder().withKeyCode(KeyEvent.KEYCODE_DEL).build())
}
}
@Test
fun closeSoftKeyboard_whenItOpened() {
page.editTextContentDesc.click()
SystemClock.sleep(500)
page.editTextContentDesc.closeSoftKeyboard()
}
@Test
fun preformCustomClick_onClickable() {
page.button.perform(click())
page.eventStatus.textContains(getResourceString(R.string.button_event_click))
}
@Test
fun performCustom_notExisted() {
AssertUtils.assertException { page.notExistElement.withTimeout(100).perform(click()) }
}
@Test
fun closeSoftKeyboardTest() {
page.editTextContentDesc.click()
UltronEspresso.closeSoftKeyboard()
page.imageView.isDisplayed()
}
@Test
fun customVisibilityAction() {
val text = "appended"
page.editTextContentDesc.appendText(text)
.hasText(getTargetString(R.string.button_default_content_desc) + text)
page.button.appendText(text)
}
@Test
fun notExist_customAction() {
AssertUtils.assertException { withText("not existed").withTimeout(19).appendText("asd") }
}
@Test
fun getTextActionTest_textExist() {
val text = page.appCompatTextView.getText()
Assert.assertEquals(getTargetString(R.string.app_compat_text), text)
}
@Test
fun getTextActionTest_noTextInView() {
AssertUtils.assertException { page.imageView.withTimeout(100).getText() }
}
@Test
fun getDrawable_drawableExist() {
Assert.assertNotNull(page.imageView.getDrawable())
}
@Test
fun hasDrawableTest() {
page.imageView.hasDrawable(R.drawable.ic_account)
}
@Test
fun hasDrawable_wrongResourceId() {
AssertUtils.assertException {
page.imageView.withTimeout(1000).hasDrawable(R.drawable.chandler)
}
}
@Test
fun drawableCompare() {
val actDr = page.imageView.getDrawable()
val actDr2 = page.imageView2.getDrawable()
Assert.assertTrue(actDr!!.isSameAs(actDr2!!))
}
@Test
fun hasAnyDrawable_noDrawable() {
AssertUtils.assertException {
page.emptyNotClickableImageView.withTimeout(1000).hasAnyDrawable()
}
}
@Test
fun hasAnyDrawable_imageHasDrawable() {
page.imageView.hasAnyDrawable()
}
@Test
fun getContentDesc_descIsNull() {
Assert.assertEquals(null, page.imageView.getContentDescription())
}
@Test
fun getContentDesc_descNotNull() {
Assert.assertEquals(
getTargetString(R.string.button_default_content_desc),
page.button.getContentDescription()
)
}
@Test
fun verifySoftAssertionsTest() {
UltronCommonConfig.testContext.softAnalyzer.clear()
softAssertion(false) {
withText("NotExistText").withTimeout(100).click()
withText("NotExistTestTag").withTimeout(100).click()
}
runCatching {
verifySoftAssertions()
}.onFailure { exception ->
val message = exception.message ?: throw RuntimeException("Empty exception message: $exception")
Assert.assertTrue(message.contains("NotExistText"))
Assert.assertTrue(message.contains("NotExistTestTag"))
}
}
@Test
fun softAssertionTest() {
UltronCommonConfig.testContext.softAnalyzer.clear()
AssertUtils.assertException {
softAssertion {
withText("NotExistText").withTimeout(100).click()
}
}
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/espresso/ViewInteractionAssertionsTest.kt
================================================
package com.atiurin.sampleapp.tests.espresso
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.atiurin.sampleapp.R
import com.atiurin.sampleapp.framework.ultronext.assertChecked
import com.atiurin.sampleapp.framework.utils.AssertUtils
import com.atiurin.sampleapp.framework.utils.TestDataUtils.getResourceString
import com.atiurin.sampleapp.pages.UiElementsPage
import com.atiurin.sampleapp.tests.UiElementsTest
import com.atiurin.ultron.core.common.assertion.softAssertion
import com.atiurin.ultron.core.common.assertion.verifySoftAssertions
import com.atiurin.ultron.core.config.UltronCommonConfig
import com.atiurin.ultron.custom.espresso.assertion.doesNotExistInAnyVisibleRoot
import com.atiurin.ultron.custom.espresso.assertion.hasCurrentHintTextColor
import com.atiurin.ultron.custom.espresso.assertion.hasCurrentTextColor
import com.atiurin.ultron.custom.espresso.assertion.hasHighlightColor
import com.atiurin.ultron.custom.espresso.assertion.hasShadowColor
import com.atiurin.ultron.custom.espresso.matcher.hasAnyDrawable
import com.atiurin.ultron.custom.espresso.matcher.withDrawable
import com.atiurin.ultron.extensions.assertMatches
import com.atiurin.ultron.extensions.click
import com.atiurin.ultron.extensions.doesNotExist
import com.atiurin.ultron.extensions.exists
import com.atiurin.ultron.extensions.hasContentDescription
import com.atiurin.ultron.extensions.hasFocus
import com.atiurin.ultron.extensions.hasText
import com.atiurin.ultron.extensions.isChecked
import com.atiurin.ultron.extensions.isClickable
import com.atiurin.ultron.extensions.isDisplayed
import com.atiurin.ultron.extensions.isEnabled
import com.atiurin.ultron.extensions.isFocusable
import com.atiurin.ultron.extensions.isJavascriptEnabled
import com.atiurin.ultron.extensions.isNotChecked
import com.atiurin.ultron.extensions.isNotClickable
import com.atiurin.ultron.extensions.isNotDisplayed
import com.atiurin.ultron.extensions.isNotEnabled
import com.atiurin.ultron.extensions.isNotFocusable
import com.atiurin.ultron.extensions.isNotSelected
import com.atiurin.ultron.extensions.isSelected
import com.atiurin.ultron.extensions.isSuccess
import com.atiurin.ultron.extensions.textContains
import com.atiurin.ultron.extensions.withTimeout
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.containsString
import org.junit.Assert
import org.junit.Test
class ViewInteractionAssertionsTest : UiElementsTest() {
val page = UiElementsPage
//displayed
@Test
fun isDisplayed_ofDisplayedObject() {
page.button.isDisplayed()
}
@Test
fun isDisplayed_ofNotDisplayedObject() {
page.radioInvisibleButton.click()
AssertUtils.assertException { page.button.withTimeout(100).isDisplayed() }
}
@Test
fun isNotDisplayed_ofDisplayedObject() {
page.radioVisibleButton.click()
AssertUtils.assertException { page.radioVisibleButton.withTimeout(100).isNotDisplayed() }
}
@Test
fun isNotDisplayed_ofNotDisplayedObject() {
page.radioInvisibleButton.click()
page.button.isNotDisplayed()
}
//doesNotExist
@Test
fun doesNotExist_notExisted() {
page.notExistElement.doesNotExist()
}
@Test
fun doesNotExist_existed() {
AssertUtils.assertException { page.button.withTimeout(100).doesNotExist() }
}
//doesNotExistAnyRoot
@Test
fun doesNotExistAnyRoot_notExisted() {
page.notExistElement.doesNotExistInAnyVisibleRoot()
}
@Test
fun doesNotExistAnyRoot_existed() {
AssertUtils.assertException { page.button.withTimeout(100).doesNotExistInAnyVisibleRoot() }
}
//exists
@Test
fun exists_ExistedHiddenView() {
page.hiddenButton.exists()
}
@Test
fun exists_NotExisted() {
AssertUtils.assertException { page.notExistElement.withTimeout(100).exists() }
}
//checked
@Test
fun isChecked_ofChecked() {
page.checkBoxEnabled.isChecked()
}
@Test
fun isChecked_ofNotChecked() {
AssertUtils.assertException { page.checkBoxSelected.withTimeout(100).isChecked() }
}
@Test
fun isNotChecked_ofChecked() {
AssertUtils.assertException { page.checkBoxClickable.withTimeout(100).isNotChecked() }
}
@Test
fun isNotChecked_ofNotChecked() {
page.checkBoxSelected.isNotChecked()
}
// selected
@Test
fun isSelected_ofSelected() {
page.checkBoxSelected.click()
page.button.isSelected()
}
@Test
fun isSelected_ofNotSelected() {
AssertUtils.assertException { page.button.withTimeout(100).isSelected() }
}
@Test
fun isNotSelected_ofSelected() {
page.checkBoxSelected.click()
AssertUtils.assertException { page.button.withTimeout(100).isNotSelected() }
}
@Test
fun isNotSelected_ofNotSelected() {
page.button.isNotSelected()
}
// enabled
@Test
fun isEnabled_ofEnabled() {
page.button.isEnabled()
}
@Test
fun isEnabled_ofNotEnabled() {
page.checkBoxEnabled.click()
AssertUtils.assertException { page.button.withTimeout(100).isEnabled() }
}
@Test
fun isNotEnabled_ofEnabled() {
AssertUtils.assertException { page.button.withTimeout(100).isNotEnabled() }
}
@Test
fun isNotEnabled_ofNotEnabled() {
page.checkBoxEnabled.click()
page.button.isNotEnabled()
}
//clickable
@Test
fun isClickable_ofClickable() {
page.button.isClickable()
}
@Test
fun isClickable_ofNotClickable() {
page.checkBoxClickable.click()
AssertUtils.assertException { page.button.withTimeout(100).isClickable() }
}
@Test
fun isNotClickable_ofClickable() {
AssertUtils.assertException { page.button.withTimeout(100).isNotClickable() }
}
@Test
fun isNotClickable_ofNotClickable() {
page.checkBoxClickable.click()
page.button.isNotClickable()
}
//focusable
@Test
fun isFocusable_ofFocusable() {
page.button.isFocusable()
}
@Test
fun isFocusable_ofNotFocusable() {
page.checkBoxFocusable.click()
AssertUtils.assertException { page.button.withTimeout(100).isFocusable() }
}
@Test
fun isNotFocusable_ofFocusable() {
AssertUtils.assertException { page.button.withTimeout(100).isNotFocusable() }
}
@Test
fun isNotFocusable_ofNotFocusable() {
page.checkBoxFocusable.click()
page.button.isNotFocusable()
}
//hasFocus
@Test
fun hasFocus_ofFocused() {
page.editTextContentDesc.click()
page.editTextContentDesc.hasFocus()
}
@Test
fun hasFocus_ofNotFocused() {
AssertUtils.assertException { page.editTextContentDesc.withTimeout(100).hasFocus() }
}
//hasText
@Test
fun hasText_CorrectText_withResourceId() {
page.editTextContentDesc.hasText(R.string.button_default_content_desc)
}
@Test
fun hasText_InvalidSubstringText() {
val text = getResourceString(R.string.button_default_content_desc)
AssertUtils.assertException { page.editTextContentDesc.withTimeout(100).hasText(text.substring(3)) }
}
@Test
fun hasText_InvalidText_withResourceId() {
AssertUtils.assertException {
page.editTextContentDesc.withTimeout(100).hasText(
R.string.action_clear_history
)
}
}
@Test
fun hasText_CorrectText_withString() {
val text = getResourceString(R.string.button_default_content_desc)
page.editTextContentDesc.hasText(text)
}
@Test
fun hasText_InvalidText_withString() {
val text = getResourceString(R.string.button_default_content_desc)
AssertUtils.assertException {
page.editTextContentDesc.withTimeout(100).hasText(
"$text to be invalid"
)
}
}
@Test
fun hasText_CorrectText_withStringMatcher() {
val text = getResourceString(R.string.button_default_content_desc)
page.editTextContentDesc.hasText(containsString(text.substring(2)))
}
@Test
fun hasText_InvalidText_withStringMatcher() {
val text = getResourceString(R.string.button_default_content_desc)
AssertUtils.assertException {
page.editTextContentDesc.withTimeout(100).hasText(
containsString("$text to be invalid")
)
}
}
//containsText
@Test
fun containsText_CorrectText_withResourceId() {
val text = getResourceString(R.string.button_default_content_desc)
page.editTextContentDesc.textContains(text.substring(3))
}
@Test
fun containsText_InvalidSubstringText() {
val text = getResourceString(R.string.button_default_content_desc)
AssertUtils.assertException {
page.editTextContentDesc.withTimeout(100).textContains(
"${text.substring(3)} to be invalid"
)
}
}
//hasContentDescription
@Test
fun hasContentDescription_CorrectText_withResourceId() {
page.button.hasContentDescription(R.string.button_default_content_desc)
}
@Test
fun hasContentDescription_InvalidSubstringText() {
val text = getResourceString(R.string.button_default_content_desc)
AssertUtils.assertException { page.button.withTimeout(100).hasContentDescription(text.substring(3)) }
}
@Test
fun hasContentDescription_InvalidText_withResourceId() {
AssertUtils.assertException {
page.button.withTimeout(100).hasContentDescription(
R.string.action_clear_history
)
}
}
@Test
fun hasContentDescription_CorrectText_withString() {
val text = getResourceString(R.string.button_default_content_desc)
page.button.hasContentDescription(text)
}
@Test
fun hasContentDescription_InvalidText_withString() {
val text = getResourceString(R.string.button_default_content_desc)
AssertUtils.assertException {
page.button.withTimeout(100).hasContentDescription(
"$text to be invalid"
)
}
}
//contentDescriptionContains
@Test
fun contentDescriptionContains_CorrectText_withString() {
val text = getResourceString(R.string.button_default_content_desc)
page.button.withTimeout(100).contentDescriptionContains(text.substring(2))
}
@Test
fun contentDescriptionContains_InvalidText_withString() {
val text = getResourceString(R.string.button_default_content_desc)
AssertUtils.assertException {
page.button.withTimeout(100).contentDescriptionContains(
"${text.substring(2)} to be invalid"
)
}
}
//assertMatches
@Test
fun assertMatches_ofMatched() {
page.button.assertMatches(allOf(isDisplayed(), isEnabled(), withText(R.string.button_text)))
}
@Test
fun assertMatches_ofNotMatched() {
page.checkBoxEnabled.click()
AssertUtils.assertException {
page.button.withTimeout(100).assertMatches(
allOf(
isDisplayed(),
isEnabled(),
withText(R.string.button_text)
),
)
}
}
//javascriptEnabled
@Test
fun jsEnabled_ofEnabled() {
page.webView.isJavascriptEnabled()
}
@Test
fun jsEnabled_ofNotEnabled() {
page.checkBoxJsEnabled.click()
AssertUtils.assertException { page.webView.withTimeout(100).isJavascriptEnabled() }
}
//isSuccess
@Test
fun isSuccess_FalseTest() {
val success = page.radioVisibleButton.isSuccess { withTimeout(100).isNotDisplayed() }
Assert.assertFalse(success)
}
@Test
fun isSuccess_TrueTest() {
val success = page.radioVisibleButton.isSuccess { withTimeout(100).isDisplayed() }
Assert.assertTrue(success)
}
@Test
fun isSuccess_NotExist_FalseTest() {
val success = page.notExistElement.isSuccess { withTimeout(100).isDisplayed() }
Assert.assertFalse(success)
}
// withAppCompatTextView
@Test
fun appCompatTextView_assertText() {
page.appCompatTextView.hasText(getResourceString(R.string.app_compat_text))
}
// hasDrawable
@Test
fun hasDrawable_viewHasDrawable() {
page.imageView.assertMatches(hasAnyDrawable())
}
@Test
fun hasDrawable_viewHasNoDrawable() {
AssertUtils.assertException { page.emptyNotClickableImageView.withTimeout(100).assertMatches(hasAnyDrawable()) }
}
@Test
fun withDrawable_correctDrawable() {
page.imageView.assertMatches(withDrawable(R.drawable.ic_account))
}
@Test
fun withDrawable_invalidDrawable() {
AssertUtils.assertException {
page.imageView.withTimeout(100).assertMatches(
withDrawable(R.drawable.ic_attach_file)
)
}
}
@Test
fun hasCurrentTextColor() {
page.eventStatus.hasCurrentTextColor(R.color.colorPrimary)
}
@Test
fun hasCurrentTextColor_invalidColor() {
AssertUtils.assertException { page.eventStatus.withTimeout(100).hasCurrentTextColor(R.color.invalid) }
}
@Test
fun hasCurrentHintTextColor() {
page.eventStatus.hasCurrentHintTextColor(R.color.colorHint)
}
@Test
fun hasCurrentHintTextColor_invalidColor() {
AssertUtils.assertException { page.eventStatus.withTimeout(100).hasCurrentHintTextColor(R.color.invalid) }
}
@Test
fun hasShadowColor() {
page.eventStatus.hasShadowColor(R.color.colorShadow)
}
@Test
fun hasShadowColor_invalidColor() {
AssertUtils.assertException { page.eventStatus.withTimeout(100).hasShadowColor(R.color.invalid) }
}
@Test
fun hasHighlightColor() {
page.eventStatus.hasHighlightColor(R.color.colorHighlight)
}
@Test
fun hasHighlightColor_invalidColor() {
AssertUtils.assertException { page.eventStatus.withTimeout(100).hasHighlightColor(R.color.invalid) }
}
@Test
fun textViewColors() {
page.eventStatus
.hasCurrentTextColor(R.color.colorPrimary)
.hasCurrentHintTextColor(R.color.colorHint)
.hasShadowColor(R.color.colorShadow)
.hasHighlightColor(R.color.colorHighlight)
}
@Test
fun appCompatTextViewTextColor() {
page.appCompatTextView.hasCurrentTextColor(R.color.colorPrimary)
}
@Test
fun appCompatTextViewTextColor_invalidColor() {
AssertUtils.assertException { page.appCompatTextView.withTimeout(100).hasCurrentTextColor(R.color.invalid) }
}
@Test
fun customAssertTest() {
page.checkBoxEnabled.assertChecked(true)
}
@Test
fun customAssertTest_invalidValue() {
AssertUtils.assertException { page.checkBoxEnabled.withTimeout(100).assertChecked(false) }
}
@Test
fun notExist_customAssertion() {
AssertUtils.assertException { withText("not exist").withTimeout(100).assertChecked(true) }
}
@Test
fun verifySoftAssertionsTest() {
UltronCommonConfig.testContext.softAnalyzer.clear()
softAssertion(false) {
withText("NotExistText").withTimeout(100).isDisplayed()
withText("NotExistTestTag").withTimeout(100).isDisplayed()
}
runCatching {
verifySoftAssertions()
}.onFailure { exception ->
val message = exception.message ?: throw RuntimeException("Empty exception message: $exception")
Assert.assertTrue(message.contains("NotExistText"))
Assert.assertTrue(message.contains("NotExistTestTag"))
}
}
@Test
fun softAssertionTest() {
UltronCommonConfig.testContext.softAnalyzer.clear()
AssertUtils.assertException {
softAssertion {
withText("NotExistText").withTimeout(100).isDisplayed()
}
}
}
}
================================================
FILE: sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/espresso/ViewTest.kt
================================================
package com.atiurin.sampleapp.tests.espresso
import android.content.Intent
import android.widget.Button
import android.widget.RadioButton
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import com.atiurin.sampleapp.R
import com.atiurin.sampleapp.activity.CustomClicksActivity
import com.atiurin.sampleapp.async.task.CompatAsyncTask.Companion.ASYNC
import com.atiurin.sampleapp.async.task.CompatAsyncTask.Companion.COMPAT_ASYNC_TASK_TIME_EXECUTION
import com.atiurin.sampleapp.framework.ultronext.getViewSimple
import com.atiurin.sampleapp.tests.BaseTest
import com.atiurin.ultron.custom.espresso.action.getView
import com.atiurin.ultron.custom.espresso.base.getViewForcibly
import com.atiurin.ultron.extensions.isChecked
import com.atiurin.ultron.extensions.perform
import com.atiurin.ultron.extensions.performOnView
import com.atiurin.ultron.extensions.performOnViewForcibly
import com.atiurin.ultron.testlifecycle.setupteardown.SetUp
import com.atiurin.ultron.testlifecycle.setupteardown.SetUpRule
import com.atiurin.ultron.testlifecycle.setupteardown.TearDown
import com.atiurin.ultron.testlifecycle.setupteardown.TearDownRule
import org.junit.Assert
import org.junit.Test
class ViewTest : BaseTest() {
private lateinit var customClicksActivity: CustomClicksActivity
private val startActivity = SetUpRule()
.add(START_ACTIVITY) {
ActivityScenario.launch(CustomClicksActivity::class.java).onActivity { activity ->
customClicksActivity = activity
}
}
.add(START_ACTIVITY_WITH_ASYNC_TASK) {
val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, CustomClicksActivity::class.java)
intent.putExtra(ASYNC, true)
ActivityScenario.launch