{"id":15463,"date":"2022-08-29T07:00:00","date_gmt":"2022-08-29T05:00:00","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=15463"},"modified":"2023-05-10T15:47:59","modified_gmt":"2023-05-10T13:47:59","slug":"wykorzystanie-funkcjonalnosci-jezyka-kotlin-do-urozmaicenia-testow-ui-pisanych-w-jezyku-java","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/wykorzystanie-funkcjonalnosci-jezyka-kotlin-do-urozmaicenia-testow-ui-pisanych-w-jezyku-java\/","title":{"rendered":"Wykorzystanie funkcjonalno\u015bci j\u0119zyka Kotlin do urozmaicenia test\u00f3w UI pisanych w j\u0119zyku Java"},"content":{"rendered":"\n<p>My \u2013 Javowcy \u2013 jeste\u015bmy skazani na paradygmat obiektowy. Nie odbierajcie tego jednak jako z\u0142ej cechy tego j\u0119zyka. Jak pokazuje instalator&nbsp;3 Billion Devices Run Java&nbsp;(niezmiennie od pocz\u0105tku tego wieku), Java wpisa\u0142a si\u0119 do kanonu programistycznego i ci\u0119\u017cko znale\u017a\u0107 osob\u0119 w IT, kt\u00f3ra o tym j\u0119zyku nie s\u0142ysza\u0142a.<\/p>\n\n\n\n<p>Od pierwszego wydania Javy min\u0119\u0142o ju\u017c ponad \u0107wier\u0107 wieku i cho\u0107 ta wci\u0105\u017c ewoluuje, to jednej prawdy zmieni\u0107 si\u0119 nie da \u2013 Java nie zosta\u0142a stworzona z my\u015bl\u0105 o programowaniu funkcyjnym. To stwierdzenie sta\u0142o si\u0119 mniej dotkliwe po aktualizacji Java 8, kt\u00f3ra wprowadzi\u0142a <a href=\"https:\/\/sii.pl\/blog\/wyrazenie-lambda-w-javie\/?category=development-na-twardo&amp;tag=java,software-development,wyrazenie-lambda\" target=\"_blank\" rel=\"noreferrer noopener\">wyra\u017cenia lambda<\/a> oraz strumienie do naszego kodu.<\/p>\n\n\n\n<p>Dzi\u015b uniwersum JVM nie ogranicza si\u0119 tylko do Javy. Powsta\u0142o wiele wartych uwagi j\u0119zyk\u00f3w programowania. Niekt\u00f3re z nich zrobi\u0142y zwrot w stron\u0119 funkcyjnego programowania, co \u015bwietnie uzupe\u0142nia j\u0119zyk Java. <\/p>\n\n\n\n<p>W tym artykule, chcia\u0142bym przybli\u017cy\u0107 Wam wybrane funkcjonalno\u015bci j\u0119zyka&nbsp;Kotlin, kt\u00f3re <strong>urozmaic\u0105 oraz u\u0142atwi\u0105 pisanie test\u00f3w automatycznych UI na przyk\u0142adzie testowania webowego<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Kotlin \u2013 pierwsze kroki<\/strong><\/h2>\n\n\n\n<p>Wprowadzenie do j\u0119zyka Kotlin zosta\u0142o ju\u017c om\u00f3wione w&nbsp;<a href=\"https:\/\/sii.pl\/blog\/java-2-0-czyli-kotlin\/\" target=\"_blank\" rel=\"noreferrer noopener\">artykule Rafa\u0142a Ha\u0142asy.<\/a>&nbsp;Firma Jetbrains przygotowa\u0142a r\u00f3wnie\u017c bardzo ciekawy&nbsp;<a href=\"https:\/\/play.kotlinlang.org\/byExample\/overview\" target=\"_blank\" rel=\"noreferrer noopener\" rel=\"nofollow\" >wst\u0119p do j\u0119zyka.<\/a>&nbsp;Tych, kt\u00f3rzy nie znaj\u0105 jeszcze Kotlina, zach\u0119cam do zapoznania si\u0119 zar\u00f3wno z artyku\u0142em jak i tutorialem.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>U\u017cywanie j\u0119zyka Kotlin w projekcie<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>\u015arodowisko IntelliJ<\/strong><\/h3>\n\n\n\n<p>W przypadku IntelliJ, wi\u0119kszo\u015b\u0107 pracy przy dodawaniu wsparcia dla j\u0119zyka Kotlin wykona za nas IDE. U\u017cytkownik musi doda\u0107 plugin Kotlin w IDE i utworzy\u0107 w strukturze projektu nowy plik kotlinowy (.kt). IntelliJ automatycznie wykryje u\u017cycie Kotlina i zapyta, czy chcemy skonfigurowa\u0107 projekt. Dokonane zostan\u0105 automatycznie niezb\u0119dne zmiany w dependency (dodanie kotlin-stdlib) i pluginie.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Inne \u015brodowisko<\/strong><\/h3>\n\n\n\n<p>Je\u017celi u\u017cywasz innego IDE ni\u017c IntelliJ, wykorzystaj poradnik do implementacji Kotlina w projektach&nbsp;<a href=\"https:\/\/kotlinlang.org\/docs\/gradle.html\" target=\"_blank\" rel=\"noreferrer noopener\" rel=\"nofollow\" >Gradle<\/a>&nbsp;oraz&nbsp;<a href=\"https:\/\/kotlinlang.org\/docs\/maven.html\" target=\"_blank\" rel=\"noreferrer noopener\" rel=\"nofollow\" >Maven<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Kilka ciekawostek<\/strong><\/h2>\n\n\n\n<p>J\u0119zyk Kotlin powsta\u0142 z my\u015bl\u0105 o pe\u0142nej kooperacji z j\u0119zykiem Java. Szukaj\u0105c rozwi\u0105zania problemu z kodem, mo\u017cemy r\u00f3wnie dobrze wyszukiwa\u0107 \u017ar\u00f3d\u0142a w j\u0119zyku Java jak i Kotlin. Je\u017celi skopiujemy kod Java do schowka i wkleimy go w pliku .kt, IntelliJ zapyta nas czy chcemy przet\u0142umaczy\u0107 wklejany kod na j\u0119zyk Kotlin:<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video controls src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/java-to-kotlin.mp4\"><\/video><figcaption><em>Przyk\u0142adowe t\u0142umaczenie j\u0119zyka Java na j\u0119zyk Kotlin<\/em><\/figcaption><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Dlaczego to dzia\u0142a?<\/strong><\/h3>\n\n\n\n<p>Zar\u00f3wno Java jak i Kotlin kompiluj\u0105 si\u0119 do bytecode\u2019u Java. We\u017amy dla przyk\u0142adu fragment kodu napisanego w j\u0119zyku Kotlin:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nfun main() {\n        val numbers = listOf(1, 2, 3, 4, 5).map { it * it }\n    }\n<\/pre><\/div>\n\n\n<p>IntelliJ umo\u017cliwia podgl\u0105d bytecode\u2019u, kt\u00f3ry ten fragment generuje. Opcja znajduj\u0119 si\u0119 pod \u015bcie\u017ck\u0105:<br><strong>Tools<\/strong>&nbsp;\u2192&nbsp;<strong>Kotlin<\/strong>&nbsp;\u2192&nbsp;<strong>Show Kotlin Bytecode<\/strong><\/p>\n\n\n\n<p>Cz\u0119\u015b\u0107 bytecode\u2019u jest pokazana poni\u017cej<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npublic final  static  main()V\n\nL0\n\nLINENUMBER 5 L0\n\nICONST_5\n\nANEWARRAY java\/lang\/Integer\n\nDUP\n\nICONST_0\n\nICONST_1\n\nINVOKESTATIC java\/lang\/Integer.valueOf (I)Ljava\/lang\/Integer;\n\n\u2026\n<\/pre><\/div>\n\n\n<p>Mo\u017cemy teraz ten bytecode zdekompilowa\u0107 z powrotem do j\u0119zyka Java. Rezultat jednak nie jest zach\u0119caj\u0105cy. Komputer przet\u0142umaczy\u0142 instrukcje na j\u0119zyk Java, ale styl kodu pozostawia wiele do \u017cyczenia:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npublic static  final  void  main() {\n\nIterable $this$map$iv = (Iterable)CollectionsKt.listOf(new  Integer&#x5B;]{1, 2, 3, 4, 5});\n\nint $i$f$map = false;\n\nCollection destination$iv$iv = (Collection)(new  ArrayList(CollectionsKt.collectionSizeOrDefault($this$map$iv, 10)));\n\nint $i$f$mapTo = false;\n\nIterator var6 = $this$map$iv.iterator();\n\nwhile(var6.hasNext()) {\n\nObject item$iv$iv = var6.next();\n\nint  it = ((Number)item$iv$iv).intValue();\n\nint var9 = false;\n\nInteger var11 = it * it;\n\ndestination$iv$iv.add(var11);\n\n}\n\nList numbers = (List)destination$iv$iv;\n\n}\n<\/pre><\/div>\n\n\n<p>Niemniej, jest to r\u00f3wnowa\u017cne (z punktu widzenia JVM) z kodem napisanym w j\u0119zyku Kotlin.<\/p>\n\n\n\n<p>\u015arodowisko Intellij ma dopracowan\u0105 zamian\u0119 kodu Java \u2192 Kotlin, a kod powsta\u0142y w ten spos\u00f3b jest w wi\u0119kszo\u015bci przypadk\u00f3w strukturalnie akceptowalny. Klasy Javowe mo\u017cemy bez problemu u\u017cywa\u0107 w kodzie kotlinowym. Ta relacja jest zwrotna, czyli kod Kotlinowy mo\u017cemy r\u00f3wnie dobrze zaimplemenotwa\u0107 w kodzie Javowym.<\/p>\n\n\n\n<p>We\u017amy przyk\u0142adow\u0105 klas\u0119 napisan\u0105 w j\u0119zyku Kotlin:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nclass Person(private val name: String, private val age: Int) {\n    fun greet() = println(&quot;My name is $name and I am $age ${if (age &gt; 1) &quot;years&quot; else &quot;year&quot;} old.&quot;)\n}\n<\/pre><\/div>\n\n\n<p>Teraz mo\u017cemy u\u017cy\u0107 j\u0105 w pliku .java:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npublic class Foo {\n    public static void main(String&#x5B;] args) {\n        new Person(&quot;Tom&quot;, 24).greet();\n    }\n}\n<\/pre><\/div>\n\n\n<p>Output:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nMy name is Tom and I am 24 years old.\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Funkcjonalno\u015bci j\u0119zyka Kotlin w praktyce<\/h2>\n\n\n\n<p>Przejd\u017amy do g\u0142\u00f3wnej cz\u0119\u015bci artyku\u0142u. W niej przedstawi\u0119 dwa podej\u015bcia j\u0119zyka Kotlin, kt\u00f3re pozwol\u0105 na u\u0142atwienie pisania i utrzymania kodu.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Delegate by Locator<\/h3>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p><em>Poczekaj a\u017c lod\u00f3wka b\u0119dzie widoczna i j\u0105 otw\u00f3rz. Poczekaj a\u017c zobaczysz jajka i je wyci\u0105gnij.<\/em><\/p><\/blockquote>\n\n\n\n<p>Zadaj\u0105c instrukcje do wykonania, cz\u0119sto pomijamy oczywiste szczeg\u00f3\u0142y, kt\u00f3rych jeste\u015bmy pewni, \u017ce ka\u017cdy za\u0142o\u017cy przed wykonaniem powierzonego zadania. Prostym przyk\u0142adem mo\u017ce by\u0107 klikanie w elementy na ekranie. Po prostu oczekujemy, \u017ce b\u0119d\u0105 widoczne zanim w nie klikniemy.<\/p>\n\n\n\n<p>U\u017cywaj\u0105c&nbsp;<a href=\"https:\/\/kotlinlang.org\/docs\/delegated-properties.html\" rel=\"nofollow\" >delegator\u00f3w \u201cby\u201d<\/a>&nbsp;i&nbsp;<a href=\"https:\/\/kotlinlang.org\/docs\/lambdas.html\" target=\"_blank\" rel=\"noreferrer noopener\" rel=\"nofollow\" >funkcji wy\u017cszego rz\u0119du<\/a>&nbsp;(ang. higher-order function) mo\u017cemy utworzy\u0107 przyjemny mechanizm do wyszukiwania web element\u00f3w oraz decydowania, kiedy maj\u0105 by\u0107 one interaktywne:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nprivate val usernameField by Id(&quot;username&quot;)\nprivate val passwordField by Id(&quot;password&quot;)\nprivate val loginButton by Xpath(&quot;.\/\/button&#x5B;@type=&#039;submit&#039;]&quot;) {\n    \/\/poczekaj a\u017c pola username i password zostan\u0105 wype\u0142nione\n    it.isDisplayed &amp;&amp; usernameField.getAttribute(&quot;value&quot;).isNotEmpty() &amp;&amp; passwordField.getAttribute(&quot;value&quot;).isNotEmpty()\n}\n(\u2026)\n\nfun pressLoginButton() = loginButton.click()\n<\/pre><\/div>\n\n\n<p>Takie podej\u015bcie pozwala ukry\u0107 oczekiwanie na dost\u0119pno\u015b\u0107 web element\u00f3w w metodach testowych i skupienie si\u0119 wy\u0142\u0105cznie na logice scenariusza testowego.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Jak to wygl\u0105da od kuchni<\/h4>\n\n\n\n<p><strong>Za\u0142o\u017cenia wst\u0119pne:<\/strong><\/p>\n\n\n\n<p>Projekt korzysta z bibilioteki&nbsp;<a href=\"https:\/\/www.selenium.dev\/documentation\/\" target=\"_blank\" rel=\"noreferrer noopener\" rel=\"nofollow\" >Selenium.<\/a> W projekcie istnieje referencja do WebDrivera, kt\u00f3ry operuje testami UI. Tworzymy obiekt&nbsp;<a href=\"https:\/\/www.selenium.dev\/selenium\/docs\/api\/java\/org\/openqa\/selenium\/support\/ui\/FluentWait.html\" target=\"_blank\" rel=\"noreferrer noopener\" rel=\"nofollow\" >FluentWait<\/a>, kt\u00f3ry pos\u0142u\u017cy nam do oczekiwania na web elementy.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nval wait: FluentWait&lt;WebDriver&gt; = FluentWait(driver)\n    .withTimeout(Duration.ofSeconds(System.getProperty(&quot;wait.timeout&quot;)?.toLong() ?: 10L))\n    .pollingEvery(Duration.ofMillis(200))\n    .ignoring(NoSuchElementException::class.java)\n<\/pre><\/div>\n\n\n<div class=\"wp-block-group is-layout-flow wp-block-group-is-layout-flow\"><\/div>\n\n\n\n<div class=\"wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-1 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:10%\">\n<figure class=\"wp-block-image size-full is-resized is-style-default\"><img decoding=\"async\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png\" alt=\"\" class=\"wp-image-15469\" width=\"59\" height=\"59\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png 512w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-300x300.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-150x150.png 150w\" sizes=\"(max-width: 59px) 100vw, 59px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-bottom is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:80%\">\n<p><\/p>\n\n\n\n<p><br>Obiekt&nbsp;wait&nbsp;nie musi by\u0107 polem \u017cadnej klasy. Zdefiniowanie go poza cia\u0142em klas oznacza, \u017ce dost\u0119p do tego obiektu jest globalny.<\/p>\n<\/div>\n<\/div>\n\n\n\n<p>Tworzymy abstrakcyjn\u0105 klas\u0119&nbsp;<em>Locator<\/em>&nbsp;rozszerzaj\u0105c\u0105 klas\u0119&nbsp;<a href=\"https:\/\/kotlinlang.org\/api\/latest\/jvm\/stdlib\/kotlin.properties\/-read-only-property\/\" target=\"_blank\" rel=\"noreferrer noopener\" rel=\"nofollow\" >ReadOnlyProperty.<\/a>&nbsp;Klasa&nbsp;Locator&nbsp;zawiera\u0107 b\u0119dzie ca\u0142\u0105 logik\u0119 dot. operowania na web elementach.<\/p>\n\n\n\n<p><strong>Opis parametr\u00f3w klasy:<\/strong><\/p>\n\n\n\n<p>locator \u2013 wybrany mechanizm do wyszukiwania element\u00f3w (np.: Id, Xpath, CssSelector itd.)<\/p>\n\n\n\n<p>path \u2013 warto\u015b\u0107 \u015bcie\u017cki, kt\u00f3ra wskazuje na szukany web element<\/p>\n\n\n\n<p>accessRule \u2013 funkcja przyjmuj\u0105ca jako parametr web element i zwracaj\u0105ca boolean (warunek na interakcj\u0119 z web elementem).<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nabstract class Locator(\n    private val locator: (String) -&gt; By,\n    private val path: String,\n    private val accessRule: (WebElement) -&gt; Boolean\n) : ReadOnlyProperty&lt;Any?, WebElement&gt; {\n    private val element: WebElement by lazy {\n        wait.until { driver.findElement(locator(path)) }\n    }\n \n    override fun getValue(thisRef: Any?, property: KProperty&lt;*&gt;): WebElement {\n        wait.until { accessRule(webElement) }\n        return webElement\n    }\n}\n<\/pre><\/div>\n\n\n<div class=\"wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-2 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:10%\">\n<figure class=\"wp-block-image size-full is-resized is-style-default\"><img decoding=\"async\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png\" alt=\"\" class=\"wp-image-15469\" width=\"59\" height=\"59\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png 512w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-300x300.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-150x150.png 150w\" sizes=\"(max-width: 59px) 100vw, 59px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-bottom is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:80%\">\n<p><\/p>\n\n\n\n<p><br>Delegator&nbsp;<strong>by lazy<\/strong>&nbsp;oznacza leniw\u0105 ewaluacj\u0119. Pole&nbsp;element&nbsp;nie zostanie obliczone do czasu pierwszego u\u017cycia.<\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-3 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:10%\">\n<figure class=\"wp-block-image size-full is-resized is-style-default\"><img decoding=\"async\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png\" alt=\"\" class=\"wp-image-15469\" width=\"59\" height=\"59\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png 512w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-300x300.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-150x150.png 150w\" sizes=\"(max-width: 59px) 100vw, 59px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-bottom is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:80%\">\n<p><\/p>\n\n\n\n<p><br>Metod\u0119&nbsp;getValue&nbsp;mo\u017cemy dowolnie rozszerza\u0107 o kolejne instrukcje dost\u0119pu do web elementu. S\u0105 przypadki, w kt\u00f3rych nie chcemy czeka\u0107 a\u017c obiekt b\u0119dzie widoczny. Takie obiekty mo\u017cemy oznaczy\u0107 dowoln\u0105 adnotacj\u0105 (np.:&nbsp;@Hidden). Dost\u0119p do adnotacji obiektu delegowanego uzyskamy przez argument&nbsp;property.<\/p>\n<\/div>\n<\/div>\n\n\n\n<p>Pozostaje ju\u017c tylko utworzenie potrzebnych nam klas rozszerzaj\u0105cych Locator wedle potrzeb:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nclass Xpath(\n    path: String,\n    \/\/ poczekaj a\u017c b\u0119dzie widoczny\n    accessRule: (WebElement) -&gt; Boolean = { it.isDisplayed }\n) : Locator(By::xpath, path, accessRule)\n\nclass Id(\n    id: String,\n    \/\/zwracaj zawsze\n    accessRule: (WebElement) -&gt; Boolean = { true }\n) : Locator(By::id, id, accessRule)\n<\/pre><\/div>\n\n\n<p>I u\u017cycie ich tak jak w przyk\u0142adzie powy\u017cej.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Integracja z Jav\u0105<\/h3>\n\n\n\n<p>Tak jak wspomnia\u0142em wcze\u015bniej, kod kotlinowy mo\u017cemy wykorzysta\u0107 w klasie Javowej. J\u0119zyk Java nie dysponuje jednak wyra\u017ceniem delegator\u00f3w&nbsp;by. Pomys\u0142em na integracj\u0119 jest zdefiniowanie web element\u00f3w w klasie .kt i rozszerzenie klasy .java o t\u0119 klas\u0119:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nclass MyScreenPageObject extends MyScreenKotlinPageObject {\n(\u2026)\n    public void clickLoginButton() {\n        getLoginButton().click();\n    }\n}\n<\/pre><\/div>\n\n\n<p>Gettery do zdefiniowanych przez delegatory p\u00f3l zosta\u0142y automatycznie wygenerowane na potrzeby operowania w j\u0119zyku Java, ale ca\u0142a logika dost\u0119p\u00f3w i ewaluacji element\u00f3w zosta\u0142a nienaruszona.<\/p>\n\n\n\n<div class=\"wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-4 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:10%\">\n<figure class=\"wp-block-image size-full is-resized is-style-default\"><img decoding=\"async\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png\" alt=\"\" class=\"wp-image-15469\" width=\"59\" height=\"59\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png 512w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-300x300.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-150x150.png 150w\" sizes=\"(max-width: 59px) 100vw, 59px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-bottom is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:80%\">\n<p><\/p>\n\n\n\n<p><br>Pami\u0119taj, \u017ce w tym przypadku modyfikator dost\u0119pu w klasie .kt nie mo\u017ce by\u0107 ustalony jako&nbsp;private.<\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-5 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:10%\">\n<figure class=\"wp-block-image size-full is-resized is-style-default\"><img decoding=\"async\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png\" alt=\"\" class=\"wp-image-15469\" width=\"59\" height=\"59\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png 512w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-300x300.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-150x150.png 150w\" sizes=\"(max-width: 59px) 100vw, 59px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-bottom is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:80%\">\n<p><\/p>\n\n\n\n<p><br>Wszystkie klasy kotlinowe (opr\u00f3cz abstrakcyjnych) s\u0105 w domy\u015blne&nbsp;final, czyli nie mo\u017cna po nich dziedziczy\u0107. W tym przypadku wymagane jest oznaczenie klasy .kt s\u0142owem&nbsp;<em>open<\/em>&nbsp;(odwrotno\u015b\u0107 znaczenia&nbsp;final).<\/p>\n<\/div>\n<\/div>\n\n\n\n<p>Dla por\u00f3wnania ten sam kod &#8211; Vanilla Java + Selenium (w uproszczeniu):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nclass MyScreen {\n(\u2026)\n    public void clickLoginButton() {\n        WebElement usernameField = getWait().until(ExpectedConditions.visibilityOfElementLocated(By.id(&quot;username&quot;)));\n        WebElement passwordField = getWait().until(ExpectedConditions.visibilityOfElementLocated(By.id(&quot;password&quot;)));\n        getWait().until(driver -&gt; !usernameField.getAttribute(&quot;value&quot;).isEmpty()\n                &amp;&amp;\n                !passwordField.getAttribute(&quot;value&quot;).isEmpty());\n        getWait().until(ExpectedConditions.visibilityOfElementLocated(By.xpath(&quot;.\/\/button&#x5B;@type=&#039;submit&#039;]&quot;))).click();\n    }\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">Robot pattern<\/h3>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>Na stronie logowania, wpisz nazw\u0119 u\u017cytkownika.<\/p><p>Na stronie logowania, wpisz has\u0142o.<\/p><p>Na stronie logowania, kliknij przycisk Login.<\/p><\/blockquote>\n\n\n\n<p>Wykonuj\u0105c kroki w obr\u0119bie jednej strony aplikacji webowej, znamy kontekst. Odwo\u0142ywanie si\u0119 za ka\u017cdym razem do obiektu w celu wywo\u0142ania metody mo\u017ce by\u0107 uci\u0105\u017cliwe, szczeg\u00f3lnie kiedy krok\u00f3w jest kilkana\u015bcie.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nloginPage.fillCredentialsForUser(&quot;blogersii001);\nloginPage.pressLoginButton();\nloginPage.verifyUserIsSuccessfullyLoggedIn();\n<\/pre><\/div>\n\n\n<p>Pomys\u0142em na usprawnienie jest ponownie wykorzystanie funkcji wy\u017cszego rz\u0119du i stworzenie przyjemnego mechanizmu tworzenia zakres\u00f3w (ang. Scope), w kt\u00f3rych kontekst jest znany.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Test\nfun `user can access website and log in`() {\n    mainPage {\n        verifyHeaderTitleIsEqualTo(&quot;&lt;bloger_sii\/&gt;&quot;)\n        clickOnSignInButton()\n    }\n \n    loginPage {\n        fillCredentialsForUser(&quot;blogersii001&quot;)\n        pressLoginButton()\n        verifyUserIsSuccessfullyLoggedIn()\n    }\n}\n<\/pre><\/div>\n\n\n<div class=\"wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-6 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:10%\">\n<figure class=\"wp-block-image size-full is-resized is-style-default\"><img decoding=\"async\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png\" alt=\"\" class=\"wp-image-15469\" width=\"59\" height=\"59\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png 512w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-300x300.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-150x150.png 150w\" sizes=\"(max-width: 59px) 100vw, 59px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-bottom is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:85%\">\n<p><\/p>\n\n\n\n<p><br>Funkcje w j\u0119zyku Kotlin mog\u0105 by\u0107 nazywane standardowo za pomoc\u0105&nbsp;camelCase&nbsp;lub za pomoc\u0105 dowolnego ci\u0105gu znak\u00f3w oznaczonego pomi\u0119dzy znakami <code>''<\/code> (Back quote)<\/p>\n<\/div>\n<\/div>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Jak to wygl\u0105da od kuchni<\/h3>\n\n\n\n<p>Powy\u017cej pokaza\u0142em, \u017ce etapy test\u00f3w s\u0105 podzielone na zakresy, kt\u00f3re nazywane s\u0105&nbsp;robotami. Ka\u017cdy robot posiada odniesienie do obiektu&nbsp;<a href=\"https:\/\/www.selenium.dev\/documentation\/test_practices\/encouraged\/page_object_models\/\" target=\"_blank\" rel=\"noreferrer noopener\" rel=\"nofollow\" >PageObject<\/a>, dla kt\u00f3rego wykonywany jest zestaw funkcji. Jedynymi dost\u0119pnymi funkcjami w ka\u017cdym robocie s\u0105 funkcje, kt\u00f3re zosta\u0142y zdefiniowane w klasie PageObject, dla kt\u00f3rej robot zosta\u0142 stworzony.<\/p>\n\n\n\n<p>Sekretem tego podej\u015bcia jest funkcja \u2013 robot, kt\u00f3ra przyjmuje metody jednej konkretnej strony (zdefiniowane w PageObject) i wykonuje je w kolejno\u015bci ich podania:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nfun loginPage(func: LoginPage.() -&gt; Unit) {\n    LoginPage().func()\n}\n<\/pre><\/div>\n\n\n<p>Argumentem funkcji&nbsp;loginPage&nbsp;jest funkcja, kt\u00f3ra&nbsp;<strong>wyst\u0119puje w klasie&nbsp;LoginPage&nbsp;i zwraca warto\u015b\u0107&nbsp;Unit<\/strong>&nbsp;(odpowiednik&nbsp;<em>void<\/em>&nbsp;w Java). Funkcja&nbsp;loginPage&nbsp;tworzy nowy obiekt klasy&nbsp;LoginPage&nbsp;i aplikuje do niego wszystkie podane jako argument metody.<\/p>\n\n\n\n<p>Analogicznie powstanie robot dla klasy&nbsp;MainPage&nbsp;z przyk\u0142adu powy\u017cej.<\/p>\n\n\n\n<div class=\"wp-block-columns is-not-stacked-on-mobile is-layout-flex wp-container-core-columns-is-layout-7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:10%\">\n<figure class=\"wp-block-image size-full is-resized is-style-default\"><img decoding=\"async\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png\" alt=\"\" class=\"wp-image-15469\" width=\"59\" height=\"59\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1.png 512w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-300x300.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/ikona-zarowki-1-150x150.png 150w\" sizes=\"(max-width: 59px) 100vw, 59px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-bottom is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:85%\">\n<p><\/p>\n\n\n\n<p><br>Roboty mog\u0105 by\u0107 dost\u0119pne globalnie. Zdefiniuj je w osobnym pliku (np.: robots.kt) dla czysto\u015bci projektu.<\/p>\n<\/div>\n<\/div>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Integracja z Jav\u0105<\/h3>\n\n\n\n<p>Java nie wspiera mechanizmu robot\u00f3w jako funkcji wy\u017cszego rz\u0119du. To, co mo\u017cemy zrobi\u0107 w tej sytuacji, to stworzenie funkcji-robot\u00f3w dla ju\u017c istniej\u0105cych klas PageObject napisanych w j\u0119zyku Java:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npublic class AddBlogPage {\n(...)\n\n    public void addBlogTitle(String blogTitle) {\n        (...)\n    }\n\n    public void submitBlogArticle() {\n        (...)\n    }\n}\n<\/pre><\/div>\n\n\n<p>Dla klasy Java tworzymy funkcj\u0119 &#8211; robota:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\nfun addBlogPage(func: AddBlogPage.() -&gt; Unit) {\n    AddBlogPage().func()\n}\n<\/pre><\/div>\n\n\n<p>I wykorzystujemy w testach napisanych w Kotlinie:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Test\nfun `Integrate Java class in Kotlin robot test`() {\n    addBlogPage {\n        addBlogTitle(&quot;Blogersii - Kotlin&quot;)\n        submitBlogArticle()\n    }\n}\n<\/pre><\/div>\n\n\n<p>Kto\u015b mo\u017ce zapyta\u0107:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>Okej, ale po co to robi\u0107, skoro znamy Builder design pattern i method chaining, kt\u00f3re s\u0105 dost\u0119pne w zwyk\u0142ej Javie?<\/p><\/blockquote>\n\n\n\n<p>Wykonanie podobnej do robot\u00f3w struktury jest mo\u017cliwe w j\u0119zyku Java, ale niesie za sob\u0105 problemy.<\/p>\n\n\n\n<p>\u017beby \u0142\u0105czy\u0107 metody w \u0142a\u0144cuchy, musza one zwraca\u0107 referencj\u0119 do obiektu, z kt\u00f3rego s\u0105 wywo\u0142ywane. Ka\u017cda metoda, kt\u00f3r\u0105 chcemy \u0142\u0105czy\u0107, musi mie\u0107 zmieniony zwracany typ z&nbsp;<em>void<\/em>&nbsp;na w\u0142asn\u0105 klas\u0119. W przypadku Kotlin refaktoryzacja kodu nie jest wymagana. Usprawnienie dodajemy bez zmian w ciele klas PageObject.<\/p>\n\n\n\n<p><strong>Przyk\u0142ad \u0142\u0105czenia metod w \u0142a\u0144cuchy \u2013 vanilla Java:<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npublic class AddBlogPage {\n\t(...)\n\n    public AddBlogPage addBlogTitle(String blogTitle) {\n        (...)\n        return this;\n    }\n\n    public AddBlogPage submitBlogArticle() {\n        (...)\n        return this;\n    }\n}\n<\/pre><\/div>\n\n\n<p><strong>U\u017cycie w testach:<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Test\nvoid javaTest(){\n\tnew AddBlogPage()\n\t.addBlogTitle(&quot;Blogersii - Kotlin&quot;)\n\t.submitBlogArticle();\n}\n<\/pre><\/div>\n\n\n<p>Drugim podej\u015bciem, nieco mniej znanym i stosowanym, do osi\u0105gni\u0119cia podobnego rezultatu w Java jest inicjalizowanie obiekt\u00f3w przy u\u017cyciu podw\u00f3jnych nawias\u00f3w (ang.&nbsp;Double brace initializaiton). W tym przypadku nie musimy zmienia\u0107 typ\u00f3w zwracanych w klasie&nbsp;AddBlogPage, poniewa\u017c referencja do klasy w \u015brodku nawias\u00f3w odbywa si\u0119 przez warto\u015b\u0107&nbsp;<strong>this<\/strong>.<\/p>\n\n\n\n<p><strong>U\u017cycie w testach:<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Test\nvoid javaTest(){\n    new AddBlogPage(){{\n        addBlogTitle(&quot;Blogersii - Kotlin&quot;);\n        submitBlogArticle();\n    }};\n}\n<\/pre><\/div>\n\n\n<p>To podej\u015bcie <strong>niesie za sob\u0105 ryzyko<\/strong>. Taki spos\u00f3b inicjalizacji generuje za ka\u017cdym razem wewn\u0105trz dodatkow\u0105 klas\u0119 anonimow\u0105, kt\u00f3ra rozszerza po klasie&nbsp;PageObject. Cho\u0107 u\u017cywanie tego typu inicjalizacji w \u015bladowych ilo\u015bciach nie utrudni pracy&nbsp;Garbage Collectora, to nagminne inicjalizowanie obiekt\u00f3w w ten spos\u00f3b, mo\u017ce doprowadzi\u0107 do wyciek\u00f3w pami\u0119ci.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Podsumowanie<\/h2>\n\n\n\n<p>J\u0119zyk Kotlin wci\u0105\u017c si\u0119 rozwija. Pokazanie wszystkich ciekawostek i usprawnie\u0144 jakie oferuje, z pewno\u015bci\u0105 wykracza poza tre\u015b\u0107 jednego artyku\u0142u.<\/p>\n\n\n\n<p>Mam nadziej\u0119, \u017ce artyku\u0142 wzmocni w czytelnikach zainteresowanie j\u0119zykiem Kotlin, a zaproponowane rozwi\u0105zania (by\u0107 mo\u017ce po tw\u00f3rczych modyfikacjach) znajd\u0105 zastosowanie w Waszych projektach.<\/p>\n\n\n\n<p>Artyku\u0142 powsta\u0142 na bazie rozwijanego przeze mnie hobbystycznie frameworku do test\u00f3w UI-owych.<\/p>\n\n\n\n<p>Kod \u017ar\u00f3d\u0142owy oraz przyk\u0142ady znajduj\u0105 si\u0119 w&nbsp;<a href=\"https:\/\/github.com\/Tomkowski\/Kotlinium-web\" rel=\"nofollow\" >publicznym repozytorium.<\/a><\/p>\n\n\n\n<p>Sprawd\u017a r\u00f3wnie\u017c&nbsp;<a href=\"http:\/\/play.kotlinlang.org\/\" rel=\"nofollow\" >play.kotlinlang.org<\/a>, aby wypr\u00f3bowa\u0107 Kotlin online bez instalacji.<\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;15463&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;6&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5 ( votes: 6)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Wykorzystanie funkcjonalno\u015bci j\u0119zyka Kotlin do urozmaicenia test\u00f3w UI pisanych w j\u0119zyku Java&quot;,&quot;width&quot;:&quot;139.5&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 139.5px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            5\/5 ( votes: 6)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>My \u2013 Javowcy \u2013 jeste\u015bmy skazani na paradygmat obiektowy. Nie odbierajcie tego jednak jako z\u0142ej cechy tego j\u0119zyka. Jak pokazuje &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/wykorzystanie-funkcjonalnosci-jezyka-kotlin-do-urozmaicenia-testow-ui-pisanych-w-jezyku-java\/\">Continued<\/a><\/p>\n","protected":false},"author":401,"featured_media":19770,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":6,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1317],"tags":[330,983,550],"class_list":["post-15463","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-testowanie","tag-java","tag-kotlin","tag-ui"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/Wykorzystanie-funkcjonalnosci-jezyka-Kotlin-do-urozmaicenia-testow-UI-pisanych-w-jezyku-Java.jpg","category_names":["Testowanie"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/15463"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/users\/401"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/comments?post=15463"}],"version-history":[{"count":3,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/15463\/revisions"}],"predecessor-version":[{"id":21584,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/15463\/revisions\/21584"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media\/19770"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media?parent=15463"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/categories?post=15463"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/tags?post=15463"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}