Completed Test Task
commit
17bd3b7917
@ -0,0 +1,16 @@
|
|||||||
|
.idea/workspace.xml
|
||||||
|
*.xml
|
||||||
|
!/**/assembly.xml
|
||||||
|
!/**/pom.xml
|
||||||
|
!/**/*log4j2*.xml
|
||||||
|
!/**/extent-config.xml
|
||||||
|
tests/allure-results
|
||||||
|
tests/resources/videos
|
||||||
|
buid/
|
||||||
|
target/
|
||||||
|
gradle/
|
||||||
|
.idea/
|
||||||
|
path/
|
||||||
|
out/
|
||||||
|
*.iml
|
||||||
|
*.log
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.jetbrains.kotlin.jvm' version '1.8.20'
|
||||||
|
}
|
||||||
|
|
||||||
|
def java_version = 17
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.toVersion(java_version)
|
||||||
|
targetCompatibility = JavaVersion.toVersion(java_version)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(KotlinCompile).configureEach {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = java_version.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'com.microsoft.playwright:playwright:1.34.0'
|
||||||
|
implementation 'org.testng:testng:7.8.0'
|
||||||
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.20'
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-reflect:1.8.20'
|
||||||
|
implementation 'io.cucumber:cucumber-testng:7.14.0'
|
||||||
|
implementation 'io.cucumber:cucumber-java:7.14.0'
|
||||||
|
implementation 'org.slf4j:slf4j-api:1.7.32'
|
||||||
|
implementation 'ch.qos.logback:logback-classic:1.2.6'
|
||||||
|
implementation 'org.projectlombok:lombok:1.18.30'
|
||||||
|
implementation 'org.jsoup:jsoup:1.15.4'
|
||||||
|
implementation 'org.springframework:spring-context:6.0.9'
|
||||||
|
implementation 'org.testng:testng:7.8.0'
|
||||||
|
implementation("io.rest-assured:rest-assured:5.1.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
maven {
|
||||||
|
url 'https://repo.maven.apache.org/maven2/'
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
url "https://plugins.gradle.org/m2"
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
url "https://repo1.maven.org/maven2/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
systemProperty('encryptionKey', System.getProperty('encryptionKey'))
|
||||||
|
useTestNG() {
|
||||||
|
listeners << 'core.listeners.TestNGListener'
|
||||||
|
suites('src/test/resources/testng.xml')
|
||||||
|
}
|
||||||
|
jvmArgs('--add-opens', 'java.base/java.lang=ALL-UNNAMED')
|
||||||
|
jvmArgs('--add-opens', 'java.base/java.lang.invoke=ALL-UNNAMED')
|
||||||
|
jvmArgs('--add-opens', 'java.base/java.nio=ALL-UNNAMED')
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import io.restassured.RestAssured
|
||||||
|
import io.restassured.http.ContentType
|
||||||
|
import io.restassured.response.Response
|
||||||
|
|
||||||
|
class BaseApi {
|
||||||
|
fun getRequest(url: String): Response {
|
||||||
|
return RestAssured.given()
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.get(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postRequest(url: String, body: Map<String, Any>): Response {
|
||||||
|
return RestAssured.given()
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(body)
|
||||||
|
.post(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
package core.listeners
|
||||||
|
|
||||||
|
import core.web.browser.InitialBrowser
|
||||||
|
import org.testng.ISuiteListener
|
||||||
|
import org.testng.ITestContext
|
||||||
|
import org.testng.ITestListener
|
||||||
|
import org.testng.ITestResult
|
||||||
|
|
||||||
|
class TestNGListener : ITestListener, ISuiteListener {
|
||||||
|
override fun onTestStart(result: ITestResult?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTestSuccess(result: ITestResult?) {
|
||||||
|
InitialBrowser.getInstance().destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTestFailure(result: ITestResult?) {
|
||||||
|
InitialBrowser.getInstance().destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTestSkipped(result: ITestResult?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTestFailedButWithinSuccessPercentage(result: ITestResult?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart(context: ITestContext?) {
|
||||||
|
InitialBrowser.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinish(context: ITestContext?) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package core.properties
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class PropertiesReader {
|
||||||
|
private val urlProperties = URLProperties()
|
||||||
|
|
||||||
|
init {
|
||||||
|
val urlPropertiesFile = "urls.properties"
|
||||||
|
urlProperties.loadFromProperties(loadProperty(urlPropertiesFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun urlsProperties(): URLProperties {
|
||||||
|
return urlProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadProperty(fileName: String?): Properties {
|
||||||
|
val properties = Properties()
|
||||||
|
try {
|
||||||
|
PropertiesReader::class.java.getResourceAsStream("/$fileName").use { inputStream ->
|
||||||
|
properties.load(inputStream)
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
return properties
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package core.properties
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class URLProperties {
|
||||||
|
lateinit var exampleURL: String
|
||||||
|
fun loadFromProperties(properties: Properties) {
|
||||||
|
exampleURL = properties.getProperty("exampleURL")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package core.utils.api
|
||||||
|
|
||||||
|
import io.restassured.response.Response
|
||||||
|
import org.testng.Assert
|
||||||
|
|
||||||
|
class ApiOpertations {
|
||||||
|
fun assertStatusCode(response: Response, expectedStatusCode: Int) {
|
||||||
|
Assert.assertEquals(response.statusCode, expectedStatusCode, "Status code should be $expectedStatusCode")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertFieldNotNull(response: Response, field: String) {
|
||||||
|
val value = response.jsonPath().getString(field)
|
||||||
|
Assert.assertNotNull(value, "$field field should not be null")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertIdGreaterThanZero(response: Response) {
|
||||||
|
val id = response.jsonPath().getInt("id")
|
||||||
|
Assert.assertTrue(id > 0, "ID should be greater than 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package core.utils.helpers
|
||||||
|
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
// Return logger for name
|
||||||
|
fun <T : Any> T.logger(name: String): Logger {
|
||||||
|
return LoggerFactory.getLogger(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Return logger for Java class, if companion object fix the name
|
||||||
|
fun <T : Any> logger(forClass: Class<T>): Logger {
|
||||||
|
return LoggerFactory.getLogger(unwrapCompanionClass(forClass))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unwrap companion class to enclosing class given a Java Class
|
||||||
|
fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> {
|
||||||
|
return ofClass.enclosingClass?.takeIf {
|
||||||
|
ofClass.enclosingClass.kotlin.objectInstance?.javaClass == ofClass
|
||||||
|
} ?: ofClass
|
||||||
|
}
|
||||||
|
|
||||||
|
// unwrap companion class to enclosing class given a Kotlin Class
|
||||||
|
fun <T : Any> unwrapCompanionClass(ofClass: KClass<T>): KClass<*> {
|
||||||
|
return unwrapCompanionClass(ofClass.java).kotlin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return logger for Kotlin class
|
||||||
|
fun <T : Any> logger(forClass: KClass<T>): Logger {
|
||||||
|
return logger(forClass.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return logger from extended class (or the enclosing class)
|
||||||
|
fun <T : Any> T.logger(): Logger {
|
||||||
|
return logger(this.javaClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a lazy logger property delegate for enclosing class
|
||||||
|
fun <R : Any> R.lazyLogger(): Lazy<Logger> {
|
||||||
|
return lazy { logger(this.javaClass) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a logger property delegate for enclosing class
|
||||||
|
fun <R : Any> R.injectLogger(): Lazy<Logger> {
|
||||||
|
return lazyOf(logger(this.javaClass))
|
||||||
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
package core.web.browser
|
||||||
|
|
||||||
|
import com.microsoft.playwright.Page
|
||||||
|
import com.microsoft.playwright.Playwright
|
||||||
|
import core.exeptions.BrowserException
|
||||||
|
import core.utils.helpers.logger
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
object InitialBrowser {
|
||||||
|
private const val browserName = "CHROME"
|
||||||
|
private val browserThread = ThreadLocal<InitialBrowser>()
|
||||||
|
|
||||||
|
fun getInstance(): InitialBrowser = browserThread.get() ?: synchronized(this) {
|
||||||
|
browserThread.get() ?: InitialBrowser.also { browserThread.set(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var page: Page? = null
|
||||||
|
|
||||||
|
fun getPage(): Page {
|
||||||
|
if (page == null) {
|
||||||
|
page = initialBrowser()
|
||||||
|
page?.context()?.pages()?.get(0)?.close()
|
||||||
|
if (logger().isDebugEnabled) {
|
||||||
|
logger().debug("Browser: " + page?.context()?.browser()?.browserType()?.name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return page ?: throw IllegalStateException("Page is null")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPage(page: Page) {
|
||||||
|
this.page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initialBrowser(): Page {
|
||||||
|
if (logger().isDebugEnabled) {
|
||||||
|
logger().debug("Initializing browser...")
|
||||||
|
}
|
||||||
|
return when (browserName) {
|
||||||
|
"CHROME" -> {
|
||||||
|
if (logger().isDebugEnabled) {
|
||||||
|
logger().debug("Creating driver instance: Chrome local")
|
||||||
|
}
|
||||||
|
val playwright = Playwright.create()
|
||||||
|
val context = playwright.chromium().launchPersistentContext(
|
||||||
|
Paths.get(""),
|
||||||
|
Options().browserOptions()
|
||||||
|
)
|
||||||
|
val page = context.newPage()
|
||||||
|
setPage(page)
|
||||||
|
page
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw BrowserException("Browser was not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
page?.let {
|
||||||
|
try {
|
||||||
|
if (logger().isDebugEnabled) {
|
||||||
|
logger().debug("Closing page...")
|
||||||
|
}
|
||||||
|
it.close()
|
||||||
|
if (logger().isDebugEnabled) {
|
||||||
|
logger().debug("Closing all context pages...")
|
||||||
|
}
|
||||||
|
it.context().pages().forEach { page -> page.close() }
|
||||||
|
val browser = it.context().browser()
|
||||||
|
if (browser != null) {
|
||||||
|
if (logger().isDebugEnabled) {
|
||||||
|
logger().debug("Closing browser...")
|
||||||
|
}
|
||||||
|
browser.close()
|
||||||
|
} else {
|
||||||
|
if (logger().isWarnEnabled) {
|
||||||
|
logger().warn("Browser is null, cannot close it.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logger().isDebugEnabled) {
|
||||||
|
logger().debug("Driver destroyed successfully.")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (logger().isErrorEnabled) {
|
||||||
|
logger().error("Error during driver destruction", e)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
page = null
|
||||||
|
}
|
||||||
|
} ?: run {
|
||||||
|
if (logger().isDebugEnabled) {
|
||||||
|
logger().debug("Driver destroy called with no driver present.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package core.web.browser
|
||||||
|
|
||||||
|
import com.microsoft.playwright.BrowserType
|
||||||
|
|
||||||
|
class Options {
|
||||||
|
fun browserOptions(): BrowserType.LaunchPersistentContextOptions {
|
||||||
|
val type = BrowserType.LaunchPersistentContextOptions()
|
||||||
|
val launchOptions: MutableList<String> = ArrayList()
|
||||||
|
launchOptions.add("--start-maximized")
|
||||||
|
launchOptions.add("--disable-gpu")
|
||||||
|
type.setHeadless(false)
|
||||||
|
.setArgs(launchOptions)
|
||||||
|
.setTimeout(30000.0)
|
||||||
|
.setDevtools(false)
|
||||||
|
.setChromiumSandbox(false)
|
||||||
|
.setViewportSize(null)
|
||||||
|
.setAcceptDownloads(true)
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package core.web.elements
|
||||||
|
|
||||||
|
import com.microsoft.playwright.Locator
|
||||||
|
import com.microsoft.playwright.Page
|
||||||
|
import core.utils.helpers.logger
|
||||||
|
import core.web.browser.InitialBrowser
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
class Window : Element() {
|
||||||
|
|
||||||
|
fun navigateTo(url: String) {
|
||||||
|
logger().info("Url: $url")
|
||||||
|
page.navigate(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package projects.example
|
||||||
|
|
||||||
|
import core.properties.PropertiesReader
|
||||||
|
import core.web.elements.Window
|
||||||
|
import io.cucumber.java.en.Given
|
||||||
|
|
||||||
|
class ExamplePageStepDefs {
|
||||||
|
private val window: Window = Window()
|
||||||
|
|
||||||
|
@Given("^user is on example URL$")
|
||||||
|
fun openExampleURL() {
|
||||||
|
window.navigateTo(PropertiesReader().urlsProperties().exampleURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
exampleURL=https://example.com
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
import core.BaseApi
|
||||||
|
import core.utils.api.ApiOpertations
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
|
||||||
|
class ApiTestTask {
|
||||||
|
private val baseApi = BaseApi()
|
||||||
|
private val validator = ApiOpertations()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun assertGetRequestSuccessfulAndContainsTitleField() {
|
||||||
|
val response = baseApi.getRequest("https://jsonplaceholder.typicode.com/posts/1")
|
||||||
|
|
||||||
|
validator.assertStatusCode(response, 200)
|
||||||
|
validator.assertFieldNotNull(response, "title")
|
||||||
|
|
||||||
|
println("GET request successful. Title: ${response.jsonPath().getString("title")}")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun assertPostRequestCreatesNewResource() {
|
||||||
|
val payload = mapOf("title" to "foo", "body" to "bar", "userId" to 1)
|
||||||
|
val response = baseApi.postRequest("https://jsonplaceholder.typicode.com/posts", payload)
|
||||||
|
|
||||||
|
validator.assertStatusCode(response, 201)
|
||||||
|
validator.assertIdGreaterThanZero(response)
|
||||||
|
|
||||||
|
println("POST request successful. Created resource ID: ${response.jsonPath().getInt("id")}")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import org.testng.annotations.Test
|
||||||
|
import projects.example.ExamplePageStepDefs
|
||||||
|
|
||||||
|
class UITestTask {
|
||||||
|
@Test
|
||||||
|
fun openExampleUrl() {
|
||||||
|
ExamplePageStepDefs().openExampleURL()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue