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