From a11746659935e5c6f51d076924adbd7e7489d499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Ka=C5=88ka?= <124378142+LukasKanka@users.noreply.github.com> Date: Tue, 15 Aug 2023 18:27:27 +0200 Subject: [PATCH] =?UTF-8?q?=C3=BAdr=C5=BEba?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PWLukTS/.gitignore | 4 + PWLukTS/.idea/.gitignore | 8 + PWLukTS/.idea/PWLukTS.iml | 9 + PWLukTS/.idea/misc.xml | 6 + PWLukTS/.idea/modules.xml | 8 + PWLukTS/.idea/vcs.xml | 7 + ...d716de9a-2387-475d-b8ff-c8380967c4a3.vsidx | Bin 0 -> 56597 bytes PWLukTS/.vs/PWLukTS/v17/.wsuo | Bin 0 -> 14848 bytes PWLukTS/.vs/VSWorkspaceState.json | 8 + PWLukTS/.vs/slnx.sqlite | Bin 0 -> 102400 bytes PWLukTS/README.md | 46 ++ PWLukTS/helpers.ts | 8 + PWLukTS/package-lock.json | 67 +++ PWLukTS/package.json | 13 + PWLukTS/page-objects/HomePage.ts | 83 ++++ PWLukTS/page-objects/LoginPage.ts | 7 + PWLukTS/playwright.config.ts | 78 ++++ PWLukTS/tests-examples/demo-todo-app.spec.ts | 437 ++++++++++++++++++ PWLukTS/tests/cookies.test.spec.ts | 19 + PWLukTS/tests/example.spec.ts | 18 + PWLukTS/tests/help.spec.ts | 10 + PWLukTS/tests/login.spec.ts | 1 + PWLukTS/tests/menuButton.test.spec.ts | 12 + PWLukTS/tests/primary.menu.spec.ts | 29 ++ PWLukTS/tests/search.spec.ts | 10 + PWLukTS/tests/social.test.spec.ts | 26 ++ PWLukTS/tests/test-1.spec.ts | 6 + PWLukTS/tests/tittle.spec.ts | 9 + PWLukTS/tests/vsechny.spec.ts | 89 ++++ PW_ZiveTS/.gitignore | 16 + PW_ZiveTS/.idea/PW_ZiveTS.iml | 9 + PW_ZiveTS/.idea/misc.xml | 6 + PW_ZiveTS/.idea/modules.xml | 8 + PW_ZiveTS/.idea/vcs.xml | 6 + PW_ZiveTS/.idea/workspace.xml | 99 ++++ PW_ZiveTS/e2e/example.spec.ts | 18 + PW_ZiveTS/package-lock.json | 67 +++ PW_ZiveTS/package.json | 16 + PW_ZiveTS/playwright.config.ts | 90 ++++ PW_ZiveTS/test/aa.spec.ts | 26 ++ PW_ZiveTS/test/aaa.spec.ts | 17 + PW_ZiveTS/test/example.spec.ts | 44 ++ .../tests-examples/demo-todo-app.spec.ts | 437 ++++++++++++++++++ Playwright základy na GitHub/.gitignore | 4 + .../.idea/.gitignore | 8 + .../.idea/Playwright základy na GitHub.iml | 9 + Playwright základy na GitHub/.idea/misc.xml | 6 + .../.idea/modules.xml | 8 + Playwright základy na GitHub/.idea/vcs.xml | 6 + ...Identifikácia elementov a akcie s nimi.md | 7 + Playwright základy na GitHub/Async Await.md | 158 +++++++ Playwright základy na GitHub/README.MD | 44 ++ .../fixtures/basePages.ts | 0 .../package-lock.json | 63 +++ Playwright základy na GitHub/package.json | 12 + .../page-objects copy/HomePage.ts | 45 ++ .../page-objects copy/LoginPage.ts | 71 +++ .../page-objects/HomePage.ts | 45 ++ .../page-objects/LoginPage.ts | 72 +++ .../playwright.config.ts | 77 +++ .../tests-examples/demo-todo-app.spec.ts | 437 ++++++++++++++++++ .../tests/alerts.spec.ts | 0 .../tests/assertions.spec.ts | 27 ++ .../tests/elementState.spec.ts | 18 + .../tests/example.spec copy.ts | 18 + .../tests/example.spec.ts | 18 + .../tests/home.spec.ts | 29 ++ .../tests/login.spec.ts | 79 ++++ .../tests/screenshots.spec.ts | 0 .../tests/tabs.spec.ts | 0 .../tests/upload.spec.ts | 0 Playwright základy na GitHub/Úkoly.md | 13 + .../Čo je Page Object Model (POM).md | 9 + .../...github/workflows/search_google.yml | 37 ++ Playwright_GH_TS/...github/workflows/zive.yml | 37 ++ Playwright_GH_TS/aaa/.gitignore | 4 + Playwright_GH_TS/aaa/package-lock.json | 67 +++ Playwright_GH_TS/aaa/package.json | 13 + Playwright_GH_TS/aaa/playwright.config.ts | 77 +++ .../aaa/tests-examples/demo-todo-app.spec.ts | 437 ++++++++++++++++++ Playwright_GH_TS/aaa/tests/aa.spec.ts | 26 ++ Playwright_GH_TS/aaa/tests/aaa.spec.ts | 17 + Playwright_GH_TS/aaa/tests/example.spec.ts | 18 + Playwright_GH_TS/lukan/.gitignore | 7 + Playwright_GH_TS/lukan/e2e/example.spec.ts | 18 + Playwright_GH_TS/lukan/package-lock.json | 67 +++ Playwright_GH_TS/lukan/package.json | 13 + .../lukan/page-objects/HomePage.ts | 62 +++ .../lukan/page-objects/LoginPage.ts | 5 + Playwright_GH_TS/lukan/playwright.config.ts | 77 +++ .../tests-examples/demo-todo-app.spec.ts | 437 ++++++++++++++++++ .../lukan/tests/cookies.test.spec.ts | 19 + Playwright_GH_TS/lukan/tests/example.spec.ts | 18 + Playwright_GH_TS/lukan/tests/login.spec.ts | 0 .../lukan/tests/menuButton.test.spec.ts | 14 + Playwright_GH_TS/lukan/tests/search.spec.ts | 10 + Playwright_GH_TS/lukan/tests/test-1.spec.ts | 6 + Playwright_GH_TS/search_google/.gitignore | 10 + .../search_google/package-lock.json | 67 +++ Playwright_GH_TS/search_google/package.json | 13 + .../search_google/playwright.config.ts | 90 ++++ .../tests-examples/demo-todo-app.spec.ts | 437 ++++++++++++++++++ .../search_google/tests/example.spec.ts | 33 ++ Playwright_GH_TS/zive.ts/.gitignore | 4 + Playwright_GH_TS/zive.ts/package-lock.json | 67 +++ Playwright_GH_TS/zive.ts/package.json | 13 + Playwright_GH_TS/zive.ts/playwright.config.ts | 90 ++++ .../tests-examples/demo-todo-app.spec.ts | 437 ++++++++++++++++++ .../zive.ts/tests/example.spec.ts | 11 + Playwright_GH_TS/zive/.gitignore | 7 + Playwright_GH_TS/zive/e2e/example.spec.ts | 18 + Playwright_GH_TS/zive/package-lock.json | 67 +++ Playwright_GH_TS/zive/package.json | 16 + Playwright_GH_TS/zive/playwright.config.ts | 90 ++++ .../zive/tests-examples/demo-todo-app.spec.ts | 437 ++++++++++++++++++ Playwright_GH_TS/zive/tests/example.spec.ts | 44 ++ 116 files changed, 6597 insertions(+) create mode 100644 PWLukTS/.gitignore create mode 100644 PWLukTS/.idea/.gitignore create mode 100644 PWLukTS/.idea/PWLukTS.iml create mode 100644 PWLukTS/.idea/misc.xml create mode 100644 PWLukTS/.idea/modules.xml create mode 100644 PWLukTS/.idea/vcs.xml create mode 100644 PWLukTS/.vs/PWLukTS/FileContentIndex/d716de9a-2387-475d-b8ff-c8380967c4a3.vsidx create mode 100644 PWLukTS/.vs/PWLukTS/v17/.wsuo create mode 100644 PWLukTS/.vs/VSWorkspaceState.json create mode 100644 PWLukTS/.vs/slnx.sqlite create mode 100644 PWLukTS/README.md create mode 100644 PWLukTS/helpers.ts create mode 100644 PWLukTS/package-lock.json create mode 100644 PWLukTS/package.json create mode 100644 PWLukTS/page-objects/HomePage.ts create mode 100644 PWLukTS/page-objects/LoginPage.ts create mode 100644 PWLukTS/playwright.config.ts create mode 100644 PWLukTS/tests-examples/demo-todo-app.spec.ts create mode 100644 PWLukTS/tests/cookies.test.spec.ts create mode 100644 PWLukTS/tests/example.spec.ts create mode 100644 PWLukTS/tests/help.spec.ts create mode 100644 PWLukTS/tests/login.spec.ts create mode 100644 PWLukTS/tests/menuButton.test.spec.ts create mode 100644 PWLukTS/tests/primary.menu.spec.ts create mode 100644 PWLukTS/tests/search.spec.ts create mode 100644 PWLukTS/tests/social.test.spec.ts create mode 100644 PWLukTS/tests/test-1.spec.ts create mode 100644 PWLukTS/tests/tittle.spec.ts create mode 100644 PWLukTS/tests/vsechny.spec.ts create mode 100644 PW_ZiveTS/.gitignore create mode 100644 PW_ZiveTS/.idea/PW_ZiveTS.iml create mode 100644 PW_ZiveTS/.idea/misc.xml create mode 100644 PW_ZiveTS/.idea/modules.xml create mode 100644 PW_ZiveTS/.idea/vcs.xml create mode 100644 PW_ZiveTS/.idea/workspace.xml create mode 100644 PW_ZiveTS/e2e/example.spec.ts create mode 100644 PW_ZiveTS/package-lock.json create mode 100644 PW_ZiveTS/package.json create mode 100644 PW_ZiveTS/playwright.config.ts create mode 100644 PW_ZiveTS/test/aa.spec.ts create mode 100644 PW_ZiveTS/test/aaa.spec.ts create mode 100644 PW_ZiveTS/test/example.spec.ts create mode 100644 PW_ZiveTS/tests-examples/demo-todo-app.spec.ts create mode 100644 Playwright základy na GitHub/.gitignore create mode 100644 Playwright základy na GitHub/.idea/.gitignore create mode 100644 Playwright základy na GitHub/.idea/Playwright základy na GitHub.iml create mode 100644 Playwright základy na GitHub/.idea/misc.xml create mode 100644 Playwright základy na GitHub/.idea/modules.xml create mode 100644 Playwright základy na GitHub/.idea/vcs.xml create mode 100644 Playwright základy na GitHub/11 Identifikácia elementov a akcie s nimi.md create mode 100644 Playwright základy na GitHub/Async Await.md create mode 100644 Playwright základy na GitHub/README.MD create mode 100644 Playwright základy na GitHub/fixtures/basePages.ts create mode 100644 Playwright základy na GitHub/package-lock.json create mode 100644 Playwright základy na GitHub/package.json create mode 100644 Playwright základy na GitHub/page-objects copy/HomePage.ts create mode 100644 Playwright základy na GitHub/page-objects copy/LoginPage.ts create mode 100644 Playwright základy na GitHub/page-objects/HomePage.ts create mode 100644 Playwright základy na GitHub/page-objects/LoginPage.ts create mode 100644 Playwright základy na GitHub/playwright.config.ts create mode 100644 Playwright základy na GitHub/tests-examples/demo-todo-app.spec.ts create mode 100644 Playwright základy na GitHub/tests/alerts.spec.ts create mode 100644 Playwright základy na GitHub/tests/assertions.spec.ts create mode 100644 Playwright základy na GitHub/tests/elementState.spec.ts create mode 100644 Playwright základy na GitHub/tests/example.spec copy.ts create mode 100644 Playwright základy na GitHub/tests/example.spec.ts create mode 100644 Playwright základy na GitHub/tests/home.spec.ts create mode 100644 Playwright základy na GitHub/tests/login.spec.ts create mode 100644 Playwright základy na GitHub/tests/screenshots.spec.ts create mode 100644 Playwright základy na GitHub/tests/tabs.spec.ts create mode 100644 Playwright základy na GitHub/tests/upload.spec.ts create mode 100644 Playwright základy na GitHub/Úkoly.md create mode 100644 Playwright základy na GitHub/Čo je Page Object Model (POM).md create mode 100644 Playwright_GH_TS/...github/workflows/search_google.yml create mode 100644 Playwright_GH_TS/...github/workflows/zive.yml create mode 100644 Playwright_GH_TS/aaa/.gitignore create mode 100644 Playwright_GH_TS/aaa/package-lock.json create mode 100644 Playwright_GH_TS/aaa/package.json create mode 100644 Playwright_GH_TS/aaa/playwright.config.ts create mode 100644 Playwright_GH_TS/aaa/tests-examples/demo-todo-app.spec.ts create mode 100644 Playwright_GH_TS/aaa/tests/aa.spec.ts create mode 100644 Playwright_GH_TS/aaa/tests/aaa.spec.ts create mode 100644 Playwright_GH_TS/aaa/tests/example.spec.ts create mode 100644 Playwright_GH_TS/lukan/.gitignore create mode 100644 Playwright_GH_TS/lukan/e2e/example.spec.ts create mode 100644 Playwright_GH_TS/lukan/package-lock.json create mode 100644 Playwright_GH_TS/lukan/package.json create mode 100644 Playwright_GH_TS/lukan/page-objects/HomePage.ts create mode 100644 Playwright_GH_TS/lukan/page-objects/LoginPage.ts create mode 100644 Playwright_GH_TS/lukan/playwright.config.ts create mode 100644 Playwright_GH_TS/lukan/tests-examples/demo-todo-app.spec.ts create mode 100644 Playwright_GH_TS/lukan/tests/cookies.test.spec.ts create mode 100644 Playwright_GH_TS/lukan/tests/example.spec.ts create mode 100644 Playwright_GH_TS/lukan/tests/login.spec.ts create mode 100644 Playwright_GH_TS/lukan/tests/menuButton.test.spec.ts create mode 100644 Playwright_GH_TS/lukan/tests/search.spec.ts create mode 100644 Playwright_GH_TS/lukan/tests/test-1.spec.ts create mode 100644 Playwright_GH_TS/search_google/.gitignore create mode 100644 Playwright_GH_TS/search_google/package-lock.json create mode 100644 Playwright_GH_TS/search_google/package.json create mode 100644 Playwright_GH_TS/search_google/playwright.config.ts create mode 100644 Playwright_GH_TS/search_google/tests-examples/demo-todo-app.spec.ts create mode 100644 Playwright_GH_TS/search_google/tests/example.spec.ts create mode 100644 Playwright_GH_TS/zive.ts/.gitignore create mode 100644 Playwright_GH_TS/zive.ts/package-lock.json create mode 100644 Playwright_GH_TS/zive.ts/package.json create mode 100644 Playwright_GH_TS/zive.ts/playwright.config.ts create mode 100644 Playwright_GH_TS/zive.ts/tests-examples/demo-todo-app.spec.ts create mode 100644 Playwright_GH_TS/zive.ts/tests/example.spec.ts create mode 100644 Playwright_GH_TS/zive/.gitignore create mode 100644 Playwright_GH_TS/zive/e2e/example.spec.ts create mode 100644 Playwright_GH_TS/zive/package-lock.json create mode 100644 Playwright_GH_TS/zive/package.json create mode 100644 Playwright_GH_TS/zive/playwright.config.ts create mode 100644 Playwright_GH_TS/zive/tests-examples/demo-todo-app.spec.ts create mode 100644 Playwright_GH_TS/zive/tests/example.spec.ts diff --git a/PWLukTS/.gitignore b/PWLukTS/.gitignore new file mode 100644 index 0000000..75e854d --- /dev/null +++ b/PWLukTS/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/PWLukTS/.idea/.gitignore b/PWLukTS/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/PWLukTS/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/PWLukTS/.idea/PWLukTS.iml b/PWLukTS/.idea/PWLukTS.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/PWLukTS/.idea/PWLukTS.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/PWLukTS/.idea/misc.xml b/PWLukTS/.idea/misc.xml new file mode 100644 index 0000000..639900d --- /dev/null +++ b/PWLukTS/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PWLukTS/.idea/modules.xml b/PWLukTS/.idea/modules.xml new file mode 100644 index 0000000..b46564a --- /dev/null +++ b/PWLukTS/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/PWLukTS/.idea/vcs.xml b/PWLukTS/.idea/vcs.xml new file mode 100644 index 0000000..62bd7a0 --- /dev/null +++ b/PWLukTS/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/PWLukTS/.vs/PWLukTS/FileContentIndex/d716de9a-2387-475d-b8ff-c8380967c4a3.vsidx b/PWLukTS/.vs/PWLukTS/FileContentIndex/d716de9a-2387-475d-b8ff-c8380967c4a3.vsidx new file mode 100644 index 0000000000000000000000000000000000000000..c0fa9b2a3964c706913895c04858256e38feabd6 GIT binary patch literal 56597 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 literal 0 HcmV?d00001 diff --git a/PWLukTS/.vs/PWLukTS/v17/.wsuo b/PWLukTS/.vs/PWLukTS/v17/.wsuo new file mode 100644 index 0000000000000000000000000000000000000000..2929932dea3e052fa87ecd6f3b86c72e1be746d7 GIT binary patch literal 14848 zcmeHNU2Ggz6~5~>fdU2El(Y?hh9pgBJ?`wk$4!IR`xB?ci5ojMB8y;lcI<4t_O5nz zlUT7IkopD>JQS%?9}of|)Jh;ER8<0r2cB9Lid2DwR9X;HBm`2PctIlO`)2lf#^as! zde^B<*t0rw|L?g!=bU@)x%>OKANtD=etzVy!Uw0ted5mg1LB}3KjevvPYSz?d3V;= z*IC3kqJ8)uT7iqAf?r(>imX^hSQ9JU%8uc%IIywjV2QR+{q-Af{Orv?g&t(BLr&Ex zP^^fn;w-4oBbF66+Vpv<@`ItO$#Q zNa7m|C&fUBQ+WW`DSh_#9zOs%f6lv#aa6D#CeR&)Q!nUytc&?Sg3cD39exPme_HS> zW%^?%WB5^otny)mj{-jq`~;9|_z>`6AlHEXej3QST#v)eIQt+x-b_2G{&zpDzy~S+ z-~86=uKbf}0FwVdNclelx_$Y#mw!Wd9zc?MfUjU&i={3BC266+5zm35EqF2!?0alSvHDDos0(Q$ zE9hse3;HRvqs}Hwii_fs7~C@7L0ADLtbhrc?cj+n=#Qhrqg1?rcJe3v543NB0RD%K zv!~PZhxea4lA&X9j}P%fvYft^N+S1?fp*av+hg) zeTOFZ+3|#=a28fED)L~dncHk?2RQ#i^g+n`g|{pyPTz#w|7pyZmIt|sR7m+E@(dt( z{CTVm*QJE7wP7W=J&%AsZC-h2a!Z0^JWFJ$ca0})zSf&NF$}5X`Y(fW5!^_wU$RFb z+cM<*y{D14EGSOdqi=_>3;Vxg{_F4m{@MR_{5NZJe%-c_3md%87DY(L6pSK@%(vK{kYlJ1)RQD zaq=vHmP6ZJ<5}NbuLXJdP6q7v6waN{s*XR8cSYX$2tNs>!9#i2K$gt~7UHz~7QqdS zpGPh^#6T|jzlgju_MS{2RX}RiZaIk4s*E-(uoZY(EoLG zad$JI8on&P-d$s~;{1C7+M>=|kXI5eA(uT*s5oG}KV>7;KIU!1_+10ueI}tx9m`-E znv%DfI&!MbTm4nkcozOXdiFHY1OT~QK>AJOlz2_VXJBy!*y$Q^`@GO#Nx5>u#$?$ z6G}{tWRz4Ag9_&|*>Y)M!{S!vzBxwWum1^T`otR9!W+ za9CC2$u+ln*hs_*ni0Wp;t>u9nme8-DynXnVNES)nx61fhZ>J-u|h&oji{!?Vlk+9 zJu;$%RrI49NpnP-U*i!hAEx|M2uSa}-Q&Hz@a>MTnU1%R_ZH`73$7&waVMn35QcTYZ#&?2zp@XkSGiNp{Ws_i>R5%Z z@3ZlC%60Co-`$a&yub9{f82GGv0mSQWIw%xuD_!*|BsiJ-~Ug4Am2V{!89bpj?q^q-~U?Cr!4Z_iSHiUji;OU5B~o+h(7(Fq)opd{eN7GcEI`X zbp5$ryz>zJ^LOd5z$aS3D9TR7fG_#)VU+RzxSg)Q_x&ktd+jXv*JkZg>pFBBJ)q7W z(C^kB`2QFBvw4s2hQ5FPbGX0K+epv54t76G4jfHeZ`Od=CxmT4tul{G`|++X>E+5oX?f#zep77%Y*|(HePr3% zsyd{u=Jc{gPgD*F3Cl94w`RVg79#22@qqA>)|8FN=sy)Y9N}iTstMjOv`R}s+-!#t6yVE+g z%lbdmi~heC-v6je_wfDCet(oa+Iq(~=kBNXKRL2)^><(Y@7P(qtNQ=7P;VZP{{l>5 BdR+hj literal 0 HcmV?d00001 diff --git a/PWLukTS/.vs/VSWorkspaceState.json b/PWLukTS/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..0745879 --- /dev/null +++ b/PWLukTS/.vs/VSWorkspaceState.json @@ -0,0 +1,8 @@ +{ + "ExpandedNodes": [ + "", + "\\tests" + ], + "SelectedNode": "\\tests\\cookies.test.spec.ts", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/PWLukTS/.vs/slnx.sqlite b/PWLukTS/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..96a7ac4ee9dd760a78d806066d12adb5ed9cd717 GIT binary patch literal 102400 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 h5~F8)7T_uErV^_h=14" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@types/node": { + "version": "20.2.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", + "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright-core": { + "version": "1.34.3", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.34.3.tgz", + "integrity": "sha512-2pWd6G7OHKemc5x1r1rp8aQcpvDh7goMBZlJv6Co5vCNLVcQJdhxRL09SGaY6HcyHH9aT4tiynZabMofVasBYw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=14" + } + } + } +} diff --git a/PWLukTS/package.json b/PWLukTS/package.json new file mode 100644 index 0000000..9c8322e --- /dev/null +++ b/PWLukTS/package.json @@ -0,0 +1,13 @@ +{ + "name": "lukan", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.34.3" + } +} diff --git a/PWLukTS/page-objects/HomePage.ts b/PWLukTS/page-objects/HomePage.ts new file mode 100644 index 0000000..0e5ee9a --- /dev/null +++ b/PWLukTS/page-objects/HomePage.ts @@ -0,0 +1,83 @@ +import { Locator, Page, expect +} from "@playwright/test"; +export class HomePage{ + page: Page; + cookiesButtonAccept: Locator; + cookiesButtonDecline: Locator; + menuButtonUvodniStrana: Locator; + menuButtonOMne: Locator; + menuButtonZasadyOchranyOsobnichUdaju: Locator; + menuButtonPodporovatele: Locator; + searchButton: Locator; + searchFieldInput: Locator; + // verifyTitleHomePage: Locator; + XsocialButton: Locator; + + constructor(page: Page){ + this.page = page; + this.cookiesButtonAccept = page.getByText('Accept'); + this.cookiesButtonDecline = page.getByText('Decline'); + this.menuButtonUvodniStrana = page.getByRole('link', { name: 'Úvodní stránka' }); + this.menuButtonOMne = page.getByRole('link' , { name: 'O Mně' }); + this.menuButtonZasadyOchranyOsobnichUdaju = page.getByRole('link' , { name: 'Zásady ochrany osobních údajů' }); + this.menuButtonPodporovatele = page.getByRole('link' , { name: 'Podporovatelé' }); + this.searchFieldInput = page.locator('//*[@id="search-7"]/form/label/input'); + this.searchButton = page.locator('#search-7 > form > button > svg > use'); + // this.verifyTitleHomePage = expect.toHaveTitle('Lukáš bloguje - Blog o všem možném i nemožném'); + this.XsocialButton = page.locator('#block-54 > ul > li.wp-social-link.wp-social-link-twitter.wp-block-social-link > a > svg > path'); + + } + + async gotoHome() { + await this.page.goto('https://www.lukan.cz/'); + return this; + } + + async clickCookiesButtonAccept() { + await this.cookiesButtonAccept.click(); + return this; + } + + async clickCookiesButtonDecline() { + await this.cookiesButtonDecline.click(); + return this; + } + + async clickUvodniStranaButton() { + await this.menuButtonUvodniStrana.click(); + return this; + } + + async clickOMneButton() { + await this.menuButtonOMne.click(); + return this; + } + + async clickZOOUButton() { + await this.menuButtonZasadyOchranyOsobnichUdaju.click(); + return this; + } + + async clickPodporovateleButton() { + await this.menuButtonPodporovatele.click(); + return this; + } + async enterTextSearchFields() { + await this.searchFieldInput.fill('Test'); + return this; + } + + async clickSearchButton() { + await this.searchButton.click(); + return this; + } + + async clickXsocialButton() { + await this.XsocialButton.click(); + return this; + } + + + + +} \ No newline at end of file diff --git a/PWLukTS/page-objects/LoginPage.ts b/PWLukTS/page-objects/LoginPage.ts new file mode 100644 index 0000000..304c184 --- /dev/null +++ b/PWLukTS/page-objects/LoginPage.ts @@ -0,0 +1,7 @@ +import { Locator, Page +} from "@playwright/test"; +export class LoginPage{ + page: Page; + + +} \ No newline at end of file diff --git a/PWLukTS/playwright.config.ts b/PWLukTS/playwright.config.ts new file mode 100644 index 0000000..54fc979 --- /dev/null +++ b/PWLukTS/playwright.config.ts @@ -0,0 +1,78 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + headless: false, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + /* + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, +*/ + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/PWLukTS/tests-examples/demo-todo-app.spec.ts b/PWLukTS/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..2fd6016 --- /dev/null +++ b/PWLukTS/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/PWLukTS/tests/cookies.test.spec.ts b/PWLukTS/tests/cookies.test.spec.ts new file mode 100644 index 0000000..97a5d00 --- /dev/null +++ b/PWLukTS/tests/cookies.test.spec.ts @@ -0,0 +1,19 @@ +import { test, expect } from '@playwright/test'; +import { HomePage } from '../page-objects/HomePage'; + + +// test odsouhlasení cookies +test('HomePage click Accept cookies', async ({ page }) => { + + const homePage = new HomePage(page); + await (await homePage.gotoHome()) + .clickCookiesButtonAccept(); + }); + + + // test zamítnutí cookies +test('HomaPage click Decline cookies' , async ({page}) => { + const homePage = new HomePage(page); + await (await homePage.gotoHome()) + .clickCookiesButtonDecline(); +}); \ No newline at end of file diff --git a/PWLukTS/tests/example.spec.ts b/PWLukTS/tests/example.spec.ts new file mode 100644 index 0000000..c511525 --- /dev/null +++ b/PWLukTS/tests/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects the URL to contain intro. + await expect(page).toHaveURL(/.*intro/); +}); diff --git a/PWLukTS/tests/help.spec.ts b/PWLukTS/tests/help.spec.ts new file mode 100644 index 0000000..501f8e4 --- /dev/null +++ b/PWLukTS/tests/help.spec.ts @@ -0,0 +1,10 @@ +import { test, expect } from '@playwright/test'; +import { loadHomepage, cokieeAccept + } from "../helpers"; + + test('1' , async ({ page }) => { + await loadHomepage(page); + await cokieeAccept(page); + + + }); \ No newline at end of file diff --git a/PWLukTS/tests/login.spec.ts b/PWLukTS/tests/login.spec.ts new file mode 100644 index 0000000..bd0afac --- /dev/null +++ b/PWLukTS/tests/login.spec.ts @@ -0,0 +1 @@ +// login deaktivován \ No newline at end of file diff --git a/PWLukTS/tests/menuButton.test.spec.ts b/PWLukTS/tests/menuButton.test.spec.ts new file mode 100644 index 0000000..37ca867 --- /dev/null +++ b/PWLukTS/tests/menuButton.test.spec.ts @@ -0,0 +1,12 @@ +import { test, expect } from '@playwright/test'; +import { HomePage } from '../page-objects/HomePage'; + +test('Click menu button with HomePage' , async ({ page }) => { + const homePage = new HomePage(page); + await (await (await (await (await (await homePage.gotoHome()) + .clickCookiesButtonAccept()) + .clickUvodniStranaButton()) + .clickOMneButton()) + .clickZOOUButton()) + .clickPodporovateleButton(); +}); \ No newline at end of file diff --git a/PWLukTS/tests/primary.menu.spec.ts b/PWLukTS/tests/primary.menu.spec.ts new file mode 100644 index 0000000..6fe9b29 --- /dev/null +++ b/PWLukTS/tests/primary.menu.spec.ts @@ -0,0 +1,29 @@ +import { test, expect } from '@playwright/test'; +import { HomePage } from '../page-objects/HomePage'; + + + +// test Textů v menu +test('Otestování textů v menu', async ({ page }) => { + + const verifyTextMenu = [ + "Úvodní stránka", + "O mně", + "Zásady ochrany osobních údajů", + "Podporovatelé" + ]; + + const homepage = new HomePage(page); + await homepage.gotoHome(); + + + const navLinks = page.locator('#menu-menu-1 li[id*=menu]'); + + for (const el of await navLinks.elementHandles()) { + console.log(await el.textContent()); + } + + expect(await navLinks.allTextContents()).toEqual(verifyTextMenu); + + + }); \ No newline at end of file diff --git a/PWLukTS/tests/search.spec.ts b/PWLukTS/tests/search.spec.ts new file mode 100644 index 0000000..0f781be --- /dev/null +++ b/PWLukTS/tests/search.spec.ts @@ -0,0 +1,10 @@ +import { test, expect } from '@playwright/test'; +import { HomePage } from '../page-objects/HomePage'; + +test('Search web' , async ({ page }) => { + const homePage = new HomePage(page); + await homePage.gotoHome(); + await homePage.enterTextSearchFields(); + await homePage.clickSearchButton(); + +}); \ No newline at end of file diff --git a/PWLukTS/tests/social.test.spec.ts b/PWLukTS/tests/social.test.spec.ts new file mode 100644 index 0000000..09bd351 --- /dev/null +++ b/PWLukTS/tests/social.test.spec.ts @@ -0,0 +1,26 @@ +import { test, expect } from '@playwright/test'; +import { HomePage } from '../page-objects/HomePage'; + + + +test('Sociální sítě X- kliknutí na Homepage' , async ({page}) => { + const homePage = new HomePage(page); + await homePage.gotoHome(); + // Klikne na ikonu Twitter a přesměrujeme se na web Twitter + await homePage.clickXsocialButton(); + const expectedUrl = 'https://twitter.com/KankysCZ'; + // Kontrola odkazu z prokliku že jsem na správné stránce, než nám Musk změní adresu + const currentUrl = await page.url(); + if (currentUrl === expectedUrl) { + console.log('Super jsi na správné stránce!'); + } else { + console.log(`Průser jsi nejsi na správné stránce. Jsi tu ${currentUrl}`); + } + + + +}); + +test('Sociální sítě LinkId - kliknutí na Hompage', ({page}) => { + const homePage = new HomePage(page); +}) \ No newline at end of file diff --git a/PWLukTS/tests/test-1.spec.ts b/PWLukTS/tests/test-1.spec.ts new file mode 100644 index 0000000..29fdff8 --- /dev/null +++ b/PWLukTS/tests/test-1.spec.ts @@ -0,0 +1,6 @@ +import { test, expect } from '@playwright/test'; + +test('test', async ({ page }) => { + await page.goto('https://lukan.cz/'); + await page.getByRole('link', { name: 'Úvodní stránka' }).click(); +}); \ No newline at end of file diff --git a/PWLukTS/tests/tittle.spec.ts b/PWLukTS/tests/tittle.spec.ts new file mode 100644 index 0000000..7f14f65 --- /dev/null +++ b/PWLukTS/tests/tittle.spec.ts @@ -0,0 +1,9 @@ +import { test, expect } from '@playwright/test'; +import { HomePage } from '../page-objects/HomePage'; + +test('Kontrola titulku', async ({ page }) => { + const homePage = new HomePage(page); + await homePage.gotoHome(); + await expect(page).toHaveTitle('Lukáš bloguje - Blog o všem možném i nemožném'); + +}); \ No newline at end of file diff --git a/PWLukTS/tests/vsechny.spec.ts b/PWLukTS/tests/vsechny.spec.ts new file mode 100644 index 0000000..7353c4b --- /dev/null +++ b/PWLukTS/tests/vsechny.spec.ts @@ -0,0 +1,89 @@ +import { test, expect } from '@playwright/test'; +import { HomePage } from '../page-objects/HomePage'; + +test.describe('Kolekce testů', () => { + test('Kontrola titulku', async ({ page }) => { + const homePage = new HomePage(page); + await homePage.gotoHome(); + await expect(page).toHaveTitle('Lukáš bloguje - Blog o všem možném i nemožném'); + + }); + + test('Kontrola textu na stránce', async ({ page }) => { + const homePage = new HomePage(page); + await homePage.gotoHome(); + + const headingText = await page.locator('text=Lukáš bloguje Blog o všem možném i nemožném '); + await expect(headingText).toBeVisible(); + + await expect(page).toHaveURL(/./); + + }); + + test('HomePage click Accept cookies', async ({ page }) => { + + const homePage = new HomePage(page); + await homePage.gotoHome(); + await homePage.clickCookiesButtonAccept(); + }); + + + // test zamítnutí cookies + test('HomaPage click Decline cookies' , async ({page}) => { + const homePage = new HomePage(page); + await homePage.gotoHome(); + await homePage.clickCookiesButtonDecline(); + }) + + test('Click menu button with HomePage' , async ({ page }) => { + const homePage = new HomePage(page); + await homePage.gotoHome(); + await homePage.clickUvodniStranaButton(); + await homePage.clickOMneButton(); + await homePage.clickZOOUButton(); + await homePage.clickPodporovateleButton(); + }); + + test('Search web' , async ({ page }) => { + const homePage = new HomePage(page); + await homePage.gotoHome(); + await homePage.enterTextSearchFields(); + await homePage.clickSearchButton(); + + }); + + test('test', async ({ page }) => { + await page.goto('https://lukan.cz/'); + await page.getByRole('link', { name: 'Úvodní stránka' }).click(); + }); + + test('Otestování textů v menu', async ({ page }) => { + + const verifyTextMenu = [ + "Úvodní stránka", + "O mně", + "Zásady ochrany osobních údajů", + "Podporovatelé" + ]; + + await page.goto('https://lukan.cz/') + + + const navLinks = page.locator('#menu-menu-1 li[id*=menu]'); + + for (const el of await navLinks.elementHandles()) { + console.log(await el.textContent()); + } + + expect(await navLinks.allTextContents()).toEqual(verifyTextMenu); + + + }); + + + + + + +}); + diff --git a/PW_ZiveTS/.gitignore b/PW_ZiveTS/.gitignore new file mode 100644 index 0000000..706b4ea --- /dev/null +++ b/PW_ZiveTS/.gitignore @@ -0,0 +1,16 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ +/test-results/ +/playwright-report/ +/playwright/.cache/ +/test-results/ +/playwright-report/ +/playwright/.cache/ +/test-results/ +/playwright-report/ +/playwright/.cache/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/PW_ZiveTS/.idea/PW_ZiveTS.iml b/PW_ZiveTS/.idea/PW_ZiveTS.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/PW_ZiveTS/.idea/PW_ZiveTS.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/PW_ZiveTS/.idea/misc.xml b/PW_ZiveTS/.idea/misc.xml new file mode 100644 index 0000000..639900d --- /dev/null +++ b/PW_ZiveTS/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PW_ZiveTS/.idea/modules.xml b/PW_ZiveTS/.idea/modules.xml new file mode 100644 index 0000000..2b302f1 --- /dev/null +++ b/PW_ZiveTS/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/PW_ZiveTS/.idea/vcs.xml b/PW_ZiveTS/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/PW_ZiveTS/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PW_ZiveTS/.idea/workspace.xml b/PW_ZiveTS/.idea/workspace.xml new file mode 100644 index 0000000..6689e56 --- /dev/null +++ b/PW_ZiveTS/.idea/workspace.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1692115830712 + + + + + + + + + + file://$PROJECT_DIR$/test/aaa.spec.ts + 5 + + + + + + \ No newline at end of file diff --git a/PW_ZiveTS/e2e/example.spec.ts b/PW_ZiveTS/e2e/example.spec.ts new file mode 100644 index 0000000..54a906a --- /dev/null +++ b/PW_ZiveTS/e2e/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects page to have a heading with the name of Installation. + await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +}); diff --git a/PW_ZiveTS/package-lock.json b/PW_ZiveTS/package-lock.json new file mode 100644 index 0000000..5cc717c --- /dev/null +++ b/PW_ZiveTS/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "zive", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "zive", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.37.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.0.tgz", + "integrity": "sha512-181WBLk4SRUyH1Q96VZl7BP6HcK0b7lbdeKisn3N/vnjitk+9HbdlFz/L5fey05vxaAhldIDnzo8KUoy8S3mmQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.37.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@types/node": { + "version": "18.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", + "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright-core": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.0.tgz", + "integrity": "sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + } + } +} diff --git a/PW_ZiveTS/package.json b/PW_ZiveTS/package.json new file mode 100644 index 0000000..48f1c5b --- /dev/null +++ b/PW_ZiveTS/package.json @@ -0,0 +1,16 @@ +{ + "name": "zive", + "version": "1.0.0", + "description": "", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.37.0" + } +} diff --git a/PW_ZiveTS/playwright.config.ts b/PW_ZiveTS/playwright.config.ts new file mode 100644 index 0000000..f389f33 --- /dev/null +++ b/PW_ZiveTS/playwright.config.ts @@ -0,0 +1,90 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './test', + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000 + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, +/* + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, +*/ + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { channel: 'chrome' }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, +}); diff --git a/PW_ZiveTS/test/aa.spec.ts b/PW_ZiveTS/test/aa.spec.ts new file mode 100644 index 0000000..66af5d1 --- /dev/null +++ b/PW_ZiveTS/test/aa.spec.ts @@ -0,0 +1,26 @@ +const { chrome } = require('playwright'); + +(async () => { + const browser = await chrome.launch({ + headless: false + }); + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto('https://www.aaaauto.cz/'); + await page.getByRole('button', { name: 'Přijmout vše' }).click(); + await page.getByRole('button', { name: 'Značka Vyberte značku' }).click(); + await page.getByText('Škoda (2793)').first().click(); + await page.getByRole('button', { name: 'Model Vyberte model' }).click(); + await page.getByText('Citigo(40)').click(); + await page.getByRole('button', { name: 'Rok Vyberte stáří vozu' }).click(); + await page.getByText('Do 10 let').click(); + await page.getByRole('button', { name: 'Cena Vyberte cenu' }).click(); + await page.getByText('Do 200 000 Kč', { exact: true }).click(); + await page.getByRole('button', { name: 'Kategorie Vyberte kategorii' }).click(); + await page.getByText('Úsporné vozy (31)').click(); + await page.getByRole('button', { name: 'Hledat' }).click(); + + // --------------------- + await context.close(); + await browser.close(); +})(); \ No newline at end of file diff --git a/PW_ZiveTS/test/aaa.spec.ts b/PW_ZiveTS/test/aaa.spec.ts new file mode 100644 index 0000000..0ac2afc --- /dev/null +++ b/PW_ZiveTS/test/aaa.spec.ts @@ -0,0 +1,17 @@ +import { test, expect } from '@playwright/test'; + +test('test', async ({ page }) => { + await page.goto('https://www.aaaauto.cz/'); + await page.getByRole('button', { name: 'Přijmout vše' }).click(); + await page.getByRole('button', { name: 'Značka Vyberte značku' }).click(); + await page.getByText('Škoda', { exact: true }).first().click(); + await page.getByRole('button', { name: 'Model Vyberte model' }).click(); + await page.getByText('Citigo(40)').click(); + await page.getByRole('button', { name: 'Rok Vyberte stáří vozu' }).click(); + await page.getByText('Do 10 let').click(); + await page.getByRole('button', { name: 'Cena Vyberte cenu' }).click(); + await page.getByText('Do 200 000 Kč', { exact: true }).click(); + await page.getByRole('button', { name: 'Kategorie Vyberte kategorii' }).click(); + await page.locator('#hpFilterNG').getByText('Úsporné vozy').click(); + await page.getByRole('button', { name: 'Hledat' }).click(); +}); \ No newline at end of file diff --git a/PW_ZiveTS/test/example.spec.ts b/PW_ZiveTS/test/example.spec.ts new file mode 100644 index 0000000..3914e7f --- /dev/null +++ b/PW_ZiveTS/test/example.spec.ts @@ -0,0 +1,44 @@ +import { test, expect } from '@playwright/test'; +import { chromium } from '@playwright/test'; + + + test('has title', async ({ page }) => { + + await page.goto('https://zive.cz'); + + // Odsouhlasí cookies + await page.click("//button[@id='didomi-notice-agree-button']/span"); + + // takto + //const button = await page.locator("//button[@id='didomi-notice-agree-button']"); + //await button.click(); + + // Klikne na menu + await page.click("//a[contains(text(),'Menu')]"); + + // vrátí se na hlavní stranu + await page.click("#mainFORM > nav > div > div.header > a.mn-logo"); + + // klikne do vyhledávání + await page.click("//a[@onclick='layout.menu.toggle(true)']"); + + // klikne do vyhledávacího pole + const searchInput = await page.$("#mainFORM > nav > div > div.header > div"); + await searchInput?.type('test'); + + // Potvrdíme vyhledávání stisknutím klávesy Enter + await searchInput?.press('Enter'); + + // vrátíme se na hlavní stránku + await page.click('//*[@id="mainFORM"]/div[3]/header/div[2]/div/a'); + + // Zkontrolujeme, že stránka má správný název + const pageTitle = await page.title(); + expect(pageTitle).toBe('Živě.cz – O počítačích, internetu, vědě a technice'); + + + + + + + }); diff --git a/PW_ZiveTS/tests-examples/demo-todo-app.spec.ts b/PW_ZiveTS/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..2fd6016 --- /dev/null +++ b/PW_ZiveTS/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/Playwright základy na GitHub/.gitignore b/Playwright základy na GitHub/.gitignore new file mode 100644 index 0000000..75e854d --- /dev/null +++ b/Playwright základy na GitHub/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/Playwright základy na GitHub/.idea/.gitignore b/Playwright základy na GitHub/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/Playwright základy na GitHub/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/Playwright základy na GitHub/.idea/Playwright základy na GitHub.iml b/Playwright základy na GitHub/.idea/Playwright základy na GitHub.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/Playwright základy na GitHub/.idea/Playwright základy na GitHub.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Playwright základy na GitHub/.idea/misc.xml b/Playwright základy na GitHub/.idea/misc.xml new file mode 100644 index 0000000..639900d --- /dev/null +++ b/Playwright základy na GitHub/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Playwright základy na GitHub/.idea/modules.xml b/Playwright základy na GitHub/.idea/modules.xml new file mode 100644 index 0000000..57ecd1d --- /dev/null +++ b/Playwright základy na GitHub/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Playwright základy na GitHub/.idea/vcs.xml b/Playwright základy na GitHub/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/Playwright základy na GitHub/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Playwright základy na GitHub/11 Identifikácia elementov a akcie s nimi.md b/Playwright základy na GitHub/11 Identifikácia elementov a akcie s nimi.md new file mode 100644 index 0000000..b6c7035 --- /dev/null +++ b/Playwright základy na GitHub/11 Identifikácia elementov a akcie s nimi.md @@ -0,0 +1,7 @@ +O tom ako písať Xpathy a využívať ich v testovaní sa dočítaš napríklad tu: https://www.guru99.com/xpath-selenium.html alebo tu. + +Viac o lokátoroch v Playwrighte nájdeš v dokumentácii tu.https://playwright.dev/docs/locators + +Taktiež dobrý blog o lokalizovaní elementov v napísala ambasádorka Playwrightu, Debbie: https://debbie.codes/blog/how-to-locate-elements-in-playwright/ + +Skvelým rozšírením do Chrome prehliadača je [Selectors Hub](https://chrome.google.com/webstore/detail/selectorshub/ndgimibanhlabgdgjcpbbndiehljcpfh). Ako ho využívať uvidíš v [tomto](https://youtu.be/-GpTpwOLCUM) videu. \ No newline at end of file diff --git a/Playwright základy na GitHub/Async Await.md b/Playwright základy na GitHub/Async Await.md new file mode 100644 index 0000000..fe27756 --- /dev/null +++ b/Playwright základy na GitHub/Async Await.md @@ -0,0 +1,158 @@ +[Portfolio](https://www.kutac.cz#portfolio) [Blog](https://www.kutac.cz/blog) [Kontakt](https://www.kutac.cz#kontakt) + +[Blog](https://www.kutac.cz/blog) [Weby a vše okolo](https://www.kutac.cz/weby-a-vse-okolo)     Async / await + +# Async / await + +[Kategorie](#) + +[Co na srdci, to na blogu](https://www.kutac.cz/co-na-srdci-to-na-blogu) [Weby a vše okolo](https://www.kutac.cz/weby-a-vse-okolo) [Počítače a internety](https://www.kutac.cz/pocitace-a-internety) [Erasmus a cestování](https://www.kutac.cz/erasmus-a-cestovani) + +[Tagy](#) + +[PHP](https://www.kutac.cz/php) [JavaScript](https://www.kutac.cz/javascript) [Laravel](https://www.kutac.cz/laravel) [Go](https://www.kutac.cz/golang) [Git](https://www.kutac.cz/git) [Kvalita kódu](https://www.kutac.cz/kvalita-kodu) [Bezpečnost](https://www.kutac.cz/bezpecnost) [Databáze](https://www.kutac.cz/databaze) [Windows](https://www.kutac.cz/windows) [Linux](https://www.kutac.cz/linux) [Google](https://www.kutac.cz/google) [HTML](https://www.kutac.cz/html) [CSS](https://www.kutac.cz/css) [htaccess](https://www.kutac.cz/htaccess) [Tipy & triky](https://www.kutac.cz/tiky-a-triky) [O mně](https://www.kutac.cz/o-mne) + +[Rychlé odkazy](#) + +[Seriály](https://www.kutac.cz/serialy) [Testovací data k článkům](https://testdata.kutac.cz/) [Čtenářský deník](https://www.kutac.cz/co-na-srdci-to-na-blogu/ctenarsky-denicek) + +25.04.2018 22:52 Michael 1 + +[JavaScript](https://www.kutac.cz/javascript) + +Asynchronní programování v Javascriptu bez callbacků a Promise nemusí být nutně sci-fi. Co všechno umí klíčová slova async / await? + +[![Async / await v Javascriptu](:/6ce0f100ec5a4fefb8cd737380630c3e "Async / await v Javascriptu")](https://www.kutac.cz/uploads/main-41375.jpg) + +S příchodem [Promise](https://www.kutac.cz/weby-a-vse-okolo/promise-v-javascriptu) se změnil a podstatně zjednodušil způsob, jakým je možné pracovat s asynchroními voláními v Javascriptu. Špatně škálovatelné volání callback funkcí nahradilo zpracování, které je přehledné, řetězitelné a s jednoduchým zachytáváním chyb. Od uvedení [generátorů](https://www.kutac.cz/weby-a-vse-okolo/generatory-v-javascriptu) je také možné pomocí pozastavitelných funkcí zpracovávat Promise  způsobem, který vypadá synchronně, ale na pozadí není (viz [příklad](https://www.kutac.cz/weby-a-vse-okolo/generatory-v-javascriptu#vyuziti-a-zaver) ve [článku o generátorech](https://www.kutac.cz/weby-a-vse-okolo/generatory-v-javascriptu)). Takový zápis může být mnohem čitelnější a čitelnější kód znamená snazší debugování. Až by mohlo někoho napadnout, proč něco takového není standardní součástí jazyka. A tak nám do Javascriptu přibyly slova async a await. + +## [](#async-await)Async / Await + +Klíčové slovo async je označením funkce, která je pozastavitelná, podobně jako generátory. V takto označené funkci pak lze použít klíčové slovo await. To automaticky vyřeší a přiřadí výsledek Promise do dané proměnné. Vezměme tento příklad, kdy na základě dat článku vypíšeme jméno jeho autora: + +```javascript +// Požadavek pomocí Promise +function printAuthor(postId) { + fetch(`api/posts/${postId}`) + .then(res => res.json()) + .then(post => fetch(`api/users/${post.userId}`)) + .then(res => res.json()) + .then(user => console.log(user.name})) + .catch(error => console.log(error)) +} + +printAuthor(42) // 'Douglas Adams' +``` + +Stejný příklad by při použití async / await vypadal takto: + +```javascript +// Požadavek pomocí async funkce +async function printAuthorAsync(postId) { + let res = await fetch(`api/posts/${postId}`) + const post = await res.json() + + res = await fetch(`api/users/${post.userId}`) + const user = await res.json() + + console.log(user.name) +} + +printAuthorAsync(42) // 'Douglas Adams' + + +``` + +Příklad si můžete [vyzkoušet zde](https://jsfiddle.net/xs3yvp2d/13/). + +Await prakticky říká "počkej, až se vyřeší tento Promise a pokračuj". To je důležitá vlastnost, protože await pracuje pouze s Promise. Pokud dostane cokoliv jiného, převede výsledek zase na Promise. Ten vždy vrací i samotná async funkce. Celý tento mechanismus je totiž postaven nad Promise a generátory. Async / await je ve skutečnosti jenom syntaktické pozlátko a využívá stávající funkce jazyka. + +## [](#vyhody)Výhody + +Proč jej vlastně používat? Tím, že se zapisuje v podstatě synchronně, je mnohem čitelnější a nastavení breakpointů při debugování je tak mnohem jednodušší. + +Protože každé zpracování Promise nemá odlišný scope, všechny hodnoty mohou být k dispozici v celém scopu funkce: + +```javascript +// Všechny výsledky Promise mohou být k dispozici v jednom scopu +async function foo() { + const a = await promise1() + const b = await promise2() + + return {a, b} +} + + +``` + +Taky je možné používat klasické podmínky mnohem snáze: + +```javascript +// Funkci lze jednodušše větvit podmínkami +async function foo() { + const a = await promise1() + const b = await promise2() + + if (a > b) { + return a + } + else { + return b + } +} +``` + +A zachytávání výjimkek probíhá pomocí try/catch bloků: + +```javascript +// Chyby jsou zachytávány try/catch bloky +async function foo() { + try { + const a = await promise1() + const b = await promise2() + + return {a, b} + } + catch (error) { + console.error(error) + } +} + + +``` + +## [](#zaver)Závěr + +Async / await je velmi vítaným přínosem do Javascriptu. Pro asynchronní programování nabízí mnohdy čitelnější alternativu dnes rozšířeného Promise. Je podporován v NodeJS verze > 7.10 a větší částí posledních verzí moderních [prohlížečů](https://caniuse.com/#search=await). Přesto, pokud jej hodláte použít v prohlížeči, doporučoval bych nejdříve [transformaci Babelem](https://babeljs.io/docs/plugins/transform-async-to-generator/). + +* * * + +Napadá vás kdy je lepší použít Promise a naopak? Máte s async / await nějaké zkušenosti? Podělte se s ostatními v komentářích. + +## Přidat komentář + +Tvoje jméno * + + + +Tvůj email + +Tvůj web + +Tvůj komentář * + +Položky označené * jsou povinné. Email nebude zveřejněn + +* * * + +## Komentáře + +27.03.2021 22:13 + +**Milos Leng** + +Super stranka, vysvetlenia lepsie ako v anglickych videach, ale stali mi je to malo :D + +[Odpovědět](#comment-473) + +© 2014 - 2023 All rights reserved, IČO: 01827219 \ No newline at end of file diff --git a/Playwright základy na GitHub/README.MD b/Playwright základy na GitHub/README.MD new file mode 100644 index 0000000..a67c9cc --- /dev/null +++ b/Playwright základy na GitHub/README.MD @@ -0,0 +1,44 @@ +Pozor TypeScript + + + +súboj Playwrightu a Cypressu si pozrieš na týchto odkazoch: +https://youtu.be/fncL63KRA-0 +https://www.youtube.com/live/bvvTzHmLWwY?feature=share +https://youtu.be/RwNZTjwhgXc + + + +Node.js si stiahneš na tomto odkaze: https://nodejs.org/en +npm si inštalovať nemusíš. Node.js ho už obsahuje. + +Visual Studio Code si stiahneš tu: https://code.visualstudio.com/ + + + + + + +Viac o fixtures sa dozvieš v Playwright dokumentácii tu: +https://playwright.dev/docs/api/class-fixtures + +Všetky spôsoby spúšťania testov nájdeš tu.https://playwright.dev/docs/running-tests + + + +Viac o async a await sa dočítaš tu: https://www.kutac.cz/weby-a-vse-okolo/async-await + + + +Viac o configu nájdeš v dokumentácii tu.https://playwright.dev/docs/test-configuration + + +O tom ako písať Xpathy a využívať ich v testovaní sa dočítaš napríklad tu: https://www.guru99.com/xpath-selenium.html alebo tu. + +Viac o lokátoroch v Playwrighte nájdeš v dokumentácii tu.https://playwright.dev/docs/locators + +Taktiež dobrý blog o lokalizovaní elementov v napísala ambasádorka Playwrightu, Debbie: https://debbie.codes/blog/how-to-locate-elements-in-playwright/ + +Skvelým rozšírením do Chrome prehliadača je [Selectors Hub](https://chrome.google.com/webstore/detail/selectorshub/ndgimibanhlabgdgjcpbbndiehljcpfh). Ako ho využívať uvidíš v [tomto](https://youtu.be/-GpTpwOLCUM) videu. + + diff --git a/Playwright základy na GitHub/fixtures/basePages.ts b/Playwright základy na GitHub/fixtures/basePages.ts new file mode 100644 index 0000000..e69de29 diff --git a/Playwright základy na GitHub/package-lock.json b/Playwright základy na GitHub/package-lock.json new file mode 100644 index 0000000..ad04375 --- /dev/null +++ b/Playwright základy na GitHub/package-lock.json @@ -0,0 +1,63 @@ +{ + "name": "Playwright základy na GitHub", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@playwright/test": "^1.34.3" + } + }, + "node_modules/@playwright/test": { + "version": "1.34.3", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.34.3.tgz", + "integrity": "sha512-zPLef6w9P6T/iT6XDYG3mvGOqOyb6eHaV9XtkunYs0+OzxBtrPAAaHotc0X+PJ00WPPnLfFBTl7mf45Mn8DBmw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.34.3" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@types/node": { + "version": "20.2.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", + "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright-core": { + "version": "1.34.3", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.34.3.tgz", + "integrity": "sha512-2pWd6G7OHKemc5x1r1rp8aQcpvDh7goMBZlJv6Co5vCNLVcQJdhxRL09SGaY6HcyHH9aT4tiynZabMofVasBYw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=14" + } + } + } +} diff --git a/Playwright základy na GitHub/package.json b/Playwright základy na GitHub/package.json new file mode 100644 index 0000000..824e4f4 --- /dev/null +++ b/Playwright základy na GitHub/package.json @@ -0,0 +1,12 @@ +{ + "name": "playwright_zaklady", + "scripts": { + "allTests": "playwright test" + }, + "keywords": [], + "author": "Kankys", + "description": "", + "devDependencies": { + "@playwright/test": "^1.34.3" + } +} \ No newline at end of file diff --git a/Playwright základy na GitHub/page-objects copy/HomePage.ts b/Playwright základy na GitHub/page-objects copy/HomePage.ts new file mode 100644 index 0000000..b1e2494 --- /dev/null +++ b/Playwright základy na GitHub/page-objects copy/HomePage.ts @@ -0,0 +1,45 @@ +// import třída modulů které budeme potřebovat +import { Locator, Page +} from "@playwright/test"; + +//definujeme třídy (class) +export class HomePage{ + page: Page; + menu: Locator; + title: Locator; + item: Locator; + addToCart: Locator; + cardBadge: Locator; + + // vytvoříme konstruktor třídy definuje proměnou page + constructor(page: Page) { + this.page = page; + this.menu = page.locator('#react-burger-menu-btn'); + this.title = page.getByText('Swag Labs'); + this.item = page.locator('#item_4_title_link'); + this.addToCart = page.locator('#add-to-cart-sauce-labs-backpack'); + this.cardBadge = page.locator('//span[@class="shopping_cart_badge"]'); + } + + // teď si definujeme metody na práci s elementy výše + + async clickOnMenu() { + await this.menu.click(); + } + + async clickOnItem() { + await this.item.click(); + } + + + async clickOnAddToCart() { + await this.addToCart.click(); + } + + async clickOnCardBAdge() { + await this.cardBadge.click(); +} + + +} + diff --git a/Playwright základy na GitHub/page-objects copy/LoginPage.ts b/Playwright základy na GitHub/page-objects copy/LoginPage.ts new file mode 100644 index 0000000..8005fa6 --- /dev/null +++ b/Playwright základy na GitHub/page-objects copy/LoginPage.ts @@ -0,0 +1,71 @@ +// import třída modulů které budeme potřebovat +import { Locator, Page + } from "@playwright/test"; + +//definujeme třídy (class) +export class LoginPage{ + page: Page; + userNameInput: Locator; + passwordInput: Locator; + loginButton: Locator; + invalidCredentialsErrorMessage: Locator; + requiredCredentialsErrorMassage: Locator; + lockedOutErrorMassage: Locator; + + // vytvoříme konstruktor třídy definuje proměnou page + constructor(page: Page) { + this.page = page; + this.userNameInput = page.locator('#user-name'); + this.passwordInput = page.locator('#password'); + this.loginButton = page.locator('#login-button'); + this.invalidCredentialsErrorMessage = page.getByText('Epic sadface: Username and password do not match any user in this service'); + this.requiredCredentialsErrorMassage = page.getByText('Epic sadface: Username is required'); + this.lockedOutErrorMassage = page.getByText('') + this.lockedOutErrorMassage = page.getByText('Epic sadface: Sorry, this user has been locked out.'); + } + + // teď si definujeme metody na práci s elementy výše this.userNameInput = page.locator('#user-name'); + // metoda na přejetí na stránku s Loginem + async gotoLoginPage() { + await this.page.goto('https://www.saucedemo.com/'); + } + + // metoda přihlašovacího jména + // protože chceme vepsat uživatelské jmáno použijeme metodu fill + async enterValidUsername() { + await this.userNameInput.fill('standard_user'); + } + + async enterLockedOutUser() { + await this.userNameInput.fill('locked_out_user'); +} + + // alternativy k valid budou invalid + async enterInvalidUsername() { + await this.userNameInput.fill('jmeno'); +} + + // zde zapíšeme heslo + async enterValidPassword() { + await this.passwordInput.fill('secret_sauce'); + } + + async enterInvalidPassword() { + await this.passwordInput.fill('heslo'); + } + + // klik na login button + async clickLoginButton() { + await this.loginButton.click(); + } + + + // valid metody na ´ůspěšné přihlášení zapozdříme tímto způsobem: + async login() { + await this.userNameInput.fill('standard_user'); + await this.passwordInput.fill('secret_sauce'); + await this.loginButton.click(); + } + +} + diff --git a/Playwright základy na GitHub/page-objects/HomePage.ts b/Playwright základy na GitHub/page-objects/HomePage.ts new file mode 100644 index 0000000..b1e2494 --- /dev/null +++ b/Playwright základy na GitHub/page-objects/HomePage.ts @@ -0,0 +1,45 @@ +// import třída modulů které budeme potřebovat +import { Locator, Page +} from "@playwright/test"; + +//definujeme třídy (class) +export class HomePage{ + page: Page; + menu: Locator; + title: Locator; + item: Locator; + addToCart: Locator; + cardBadge: Locator; + + // vytvoříme konstruktor třídy definuje proměnou page + constructor(page: Page) { + this.page = page; + this.menu = page.locator('#react-burger-menu-btn'); + this.title = page.getByText('Swag Labs'); + this.item = page.locator('#item_4_title_link'); + this.addToCart = page.locator('#add-to-cart-sauce-labs-backpack'); + this.cardBadge = page.locator('//span[@class="shopping_cart_badge"]'); + } + + // teď si definujeme metody na práci s elementy výše + + async clickOnMenu() { + await this.menu.click(); + } + + async clickOnItem() { + await this.item.click(); + } + + + async clickOnAddToCart() { + await this.addToCart.click(); + } + + async clickOnCardBAdge() { + await this.cardBadge.click(); +} + + +} + diff --git a/Playwright základy na GitHub/page-objects/LoginPage.ts b/Playwright základy na GitHub/page-objects/LoginPage.ts new file mode 100644 index 0000000..9720203 --- /dev/null +++ b/Playwright základy na GitHub/page-objects/LoginPage.ts @@ -0,0 +1,72 @@ + +// import třída modulů které budeme potřebovat +import { Locator, Page + } from "@playwright/test"; + +//definujeme třídy (class) +export class LoginPage{ + page: Page; + userNameInput: Locator; + passwordInput: Locator; + loginButton: Locator; + invalidCredentialsErrorMessage: Locator; + requiredCredentialsErrorMassage: Locator; + lockedOutErrorMassage: Locator; + + // vytvoříme konstruktor třídy definuje proměnou page + constructor(page: Page) { + this.page = page; + this.userNameInput = page.locator('#user-name'); + this.passwordInput = page.locator('#password'); + this.loginButton = page.locator('#login-button'); + this.invalidCredentialsErrorMessage = page.getByText('Epic sadface: Username and password do not match any user in this service'); + this.requiredCredentialsErrorMassage = page.getByText('Epic sadface: Username is required'); + this.lockedOutErrorMassage = page.getByText('') + this.lockedOutErrorMassage = page.getByText('Epic sadface: Sorry, this user has been locked out.'); + } + + // teď si definujeme metody na práci s elementy výše this.userNameInput = page.locator('#user-name'); + // metoda na přejetí na stránku s Loginem + async gotoLoginPage() { + await this.page.goto('https://www.saucedemo.com/'); + } + + // metoda přihlašovacího jména + // protože chceme vepsat uživatelské jmáno použijeme metodu fill + async enterValidUsername() { + await this.userNameInput.fill('standard_user'); + } + + async enterLockedOutUser() { + await this.userNameInput.fill('locked_out_user'); +} + + // alternativy k valid budou invalid + async enterInvalidUsername() { + await this.userNameInput.fill('jmeno'); +} + + // zde zapíšeme heslo + async enterValidPassword() { + await this.passwordInput.fill('secret_sauce'); + } + + async enterInvalidPassword() { + await this.passwordInput.fill('heslo'); + } + + // klik na login button + async clickLoginButton() { + await this.loginButton.click(); + } + + + // valid metody na ´ůspěšné přihlášení zapozdříme tímto způsobem: + async login() { + await this.userNameInput.fill('standard_user'); + await this.passwordInput.fill('secret_sauce'); + await this.loginButton.click(); + } + +} + diff --git a/Playwright základy na GitHub/playwright.config.ts b/Playwright základy na GitHub/playwright.config.ts new file mode 100644 index 0000000..cdcccfe --- /dev/null +++ b/Playwright základy na GitHub/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/Playwright základy na GitHub/tests-examples/demo-todo-app.spec.ts b/Playwright základy na GitHub/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..2fd6016 --- /dev/null +++ b/Playwright základy na GitHub/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/Playwright základy na GitHub/tests/alerts.spec.ts b/Playwright základy na GitHub/tests/alerts.spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/Playwright základy na GitHub/tests/assertions.spec.ts b/Playwright základy na GitHub/tests/assertions.spec.ts new file mode 100644 index 0000000..15d7058 --- /dev/null +++ b/Playwright základy na GitHub/tests/assertions.spec.ts @@ -0,0 +1,27 @@ +import { test, expect } from '@playwright/test'; + +//pokud zapíšeme za test.only spustíme pouze jeden tento test +test('Element state', async ({ page }) => { + await page.goto('https://www.saucedemo.com'); + + //Vypíšeme zda stav elementu jestli je viditelný + await expect(page.locator('#user-name')).toBeVisible(); + //await expect(page.locator('#user-name')).toContain; + + //Vypíšeme zda stav elementu jestli je editovatelný + await expect(page.locator('#password')).toBeEditable(); + + //Vypíšeme zda stav elementu jestli je na stránce jedenkrát (to je číslo 1 v závorce) + await expect(page.locator('#login-button')).toHaveCount(1); + + //Ověří že se některý element na stránce nenáchází. Přesný opak to.BeVisible. Na stránce by se neměl nacházet tento element + await expect(page.locator('#skillmea')).not.toBeVisible(); + +}); + +// toto je idetifikovatelný element -- > await page.locator('#user-name') +// Pokud nám test někde padá nebo chci případně přeskočit místo kde padl použiji .soft v tomto místě přeskočí chybu a pkračuje dál +// await expect.soft(page.locator('#password'))not.toBeEditable(); + +// seznam všech asertů můžeš získat že za tešku vložím .toBeHave nebou .toBeContain +//await expect(page.locator('#user-name')).toContain; \ No newline at end of file diff --git a/Playwright základy na GitHub/tests/elementState.spec.ts b/Playwright základy na GitHub/tests/elementState.spec.ts new file mode 100644 index 0000000..385e360 --- /dev/null +++ b/Playwright základy na GitHub/tests/elementState.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +//pokud zapíšeme za test.only spustíme pouze jeden tento test +test('Element state', async ({ page }) => { + await page.goto('https://www.saucedemo.com'); + + //Vypíšeme zda stav elementu jestli je editovatelný + console.log(await page.locator('#user-name').isEditable()); + + //Vypíšeme zda stav elementu jestli je viditelný + console.log(await page.locator('#password').isVisible()); + + //Vypíšeme zda stav elementu jestli je skrytý + console.log(await page.locator('#login-button').isHidden()); + + + +}); \ No newline at end of file diff --git a/Playwright základy na GitHub/tests/example.spec copy.ts b/Playwright základy na GitHub/tests/example.spec copy.ts new file mode 100644 index 0000000..c511525 --- /dev/null +++ b/Playwright základy na GitHub/tests/example.spec copy.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects the URL to contain intro. + await expect(page).toHaveURL(/.*intro/); +}); diff --git a/Playwright základy na GitHub/tests/example.spec.ts b/Playwright základy na GitHub/tests/example.spec.ts new file mode 100644 index 0000000..c511525 --- /dev/null +++ b/Playwright základy na GitHub/tests/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects the URL to contain intro. + await expect(page).toHaveURL(/.*intro/); +}); diff --git a/Playwright základy na GitHub/tests/home.spec.ts b/Playwright základy na GitHub/tests/home.spec.ts new file mode 100644 index 0000000..b3521f2 --- /dev/null +++ b/Playwright základy na GitHub/tests/home.spec.ts @@ -0,0 +1,29 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../page-objects/LoginPage'; +import { HomePage } from '../page-objects/HomePage'; + +// přihlášení na HomePage a ověření textu domovské stránky. +test('Verify home title', async ({ page }) => { + const loginPage = new LoginPage(page); + const homePage = new HomePage(page); + await loginPage.gotoLoginPage(); + await loginPage.login(); + await expect(homePage.title).toBeVisible(); +}); + +// test přidání produktu do košíku a kontrola že zobrazuje 1 položka v košíku +test('Verify add to card functionality', async ({ page }) => { + const loginPage = new LoginPage(page); + const homePage = new HomePage(page); + await loginPage.gotoLoginPage(); + await loginPage.login(); + await homePage.clickOnAddToCart(); + await expect(homePage.cardBadge).toHaveText("1"); +}); + +//Odstranit produkt z košíká zkontrolovat že v něm není + +//vytvořit novou třídu pro produkt page, identifikovat elementy, napsat test na přidání do košíku product page, kliknutína tlačítko back ověřím že se dostanu zpět na homePage + + + diff --git a/Playwright základy na GitHub/tests/login.spec.ts b/Playwright základy na GitHub/tests/login.spec.ts new file mode 100644 index 0000000..aa80cba --- /dev/null +++ b/Playwright základy na GitHub/tests/login.spec.ts @@ -0,0 +1,79 @@ +/* + Zápis pomocí Page_Objects --> je kratší, čitatelnější a přehlednější. Pokud se například změní lokátor id nemusím ho měnit ve všech testech +ale, v daném page-objects +ctrl + click mě přesune na zadanou metodu!! + */ +import { test, expect } from '@playwright/test'; +//Importujeme vytvořenou třídu z page-objects +import { LoginPage } from '../page-objects/LoginPage'; + +test('Successful login', async ({ page }) => { + // nová instance loginPage odkazuje na metody co jsme si vytvořili v LoginPage + const loginPage = new LoginPage(page); + // zadáme metody co jsme vytvořily v LoginPage + await loginPage.gotoLoginPage(); + // místo tohoto použijeme námi vytvořenou metodu login z LoginPage + //await loginPage.enterValidUsername(); + //await loginPage.enterValidPassword(); + //await loginPage.clickLoginButton(); + // metoda login + await loginPage.login(); + // ověříme že jsme se úspěšně přihlásily + await expect(page).toHaveURL('https://www.saucedemo.com/inventory.html'); + /* + // způsob jeden test komplet v kódu bez odkazu na page-objects + await page.goto('https://www.saucedemo.com/'); + await page.locator('[data-test="username"]').click(); + await page.locator('[data-test="username"]').fill('standard_user'); + await page.locator('[data-test="password"]').click(); + await page.locator('[data-test="password"]').fill('secret_sauce'); + await page.locator('[data-test="login-button"]').click(); + + // Zkontrolujeme jestli na stránce je viditelný Swag Labs + await expect(page.getByText('Swag Labs')).toBeVisible(); + */ +}); + +// test přihlášení s neplatným heslem +test('Cannot login with valid username and invalid password', async ({ page }) => { + // test reporty(video 23): + + const loginPage = new LoginPage(page); + await loginPage.gotoLoginPage(); + await loginPage.enterValidUsername(); + await loginPage.enterInvalidPassword(); + await loginPage.clickLoginButton(); + await expect(loginPage.invalidCredentialsErrorMessage).toBeVisible(); +}); + + + //test s neplatným přihlašovacím jménem + test('Cannot login with invalid username and valid password', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.gotoLoginPage(); + await loginPage.enterInvalidUsername(); + await loginPage.enterValidPassword(); + await loginPage.clickLoginButton(); + await expect(loginPage.invalidCredentialsErrorMessage).toBeVisible(); + +}); + +//test pouze s kliknutím na login button +test('Cannot login with blank fields', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.gotoLoginPage(); + await loginPage.clickLoginButton(); + await expect(loginPage.requiredCredentialsErrorMassage).toBeVisible(); + +}); + +// přihlášení s zablokovaným userem +test('Cannot login with locked out user', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.gotoLoginPage(); + await loginPage.enterLockedOutUser(); + await loginPage.enterValidPassword(); + await loginPage.clickLoginButton(); + await expect(loginPage.lockedOutErrorMassage).toBeVisible(); + +}); diff --git a/Playwright základy na GitHub/tests/screenshots.spec.ts b/Playwright základy na GitHub/tests/screenshots.spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/Playwright základy na GitHub/tests/tabs.spec.ts b/Playwright základy na GitHub/tests/tabs.spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/Playwright základy na GitHub/tests/upload.spec.ts b/Playwright základy na GitHub/tests/upload.spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/Playwright základy na GitHub/Úkoly.md b/Playwright základy na GitHub/Úkoly.md new file mode 100644 index 0000000..7235adb --- /dev/null +++ b/Playwright základy na GitHub/Úkoly.md @@ -0,0 +1,13 @@ +1. vytvořit novou třídu pro produkt page, identifikovat elementy, napsat test na přidání do košíku product page, kliknutína tlačítko back ověřím že se dostanu zpět na homePage + +2. product page --> //vytvořit novou třídu pro produkt page, identifikovat elementy, napsat test na přidání do košíku product page, kliknutína tlačítko back ověřím že se dostanu zpět na homePage + +3. fixtures dodělat video 21!! + +4. report test --> test stepy + +5. video 26 alerty udělat + +6. Od 21 vše dodělat!! + + diff --git a/Playwright základy na GitHub/Čo je Page Object Model (POM).md b/Playwright základy na GitHub/Čo je Page Object Model (POM).md new file mode 100644 index 0000000..695731e --- /dev/null +++ b/Playwright základy na GitHub/Čo je Page Object Model (POM).md @@ -0,0 +1,9 @@ +Page Object Model (POM) je návrhový vzor v test automatizácii, ktorý slúži na organizáciu a správu automatizovaných testov v testovacej sade. Tento vzor zabezpečuje oddelenie testovacieho kódu od samotnej implementácie testovaných stránok. + + +Konkrétne, POM rozdeľuje testovanie webovej aplikácie na dva oddelené kódy: + +Testovací kód: tento kód zahŕňa logiku testovania, ako sú interakcie s webovými stránkami, overenie správnosti vykonania akcií a overenie výsledkov testovania. +Kód objektov stránky (Page Objects): tento kód zahŕňa definície objektov, ktoré predstavujú prvky na webovej stránke (napr. tlačidlá, polia na vstupe, odkazy atď.) a ich správanie. Každý objekt stránky má svoju vlastnú triedu, ktorá implementuje metódy pre interakciu s príslušnými prvky na stránke. + +Výhodou použitia POM je to, že testovací kód sa stáva jednoduchším a ľahšie udržiavateľným, pretože neobsahuje žiadne informácie o interakcii s prvkami na webovej stránke. Kód objektov stránky poskytuje abstraktnú reprezentáciu prvkov na webovej stránke, ktorá je potom využívaná testovacím kódom. Tento prístup umožňuje jednoduchšiu údržbu automatizovaných testov v prípade zmien na webovej stránke, pretože zmeny sa robia len v kóde objektov stránky. \ No newline at end of file diff --git a/Playwright_GH_TS/...github/workflows/search_google.yml b/Playwright_GH_TS/...github/workflows/search_google.yml new file mode 100644 index 0000000..7ab226a --- /dev/null +++ b/Playwright_GH_TS/...github/workflows/search_google.yml @@ -0,0 +1,37 @@ +name: Search Google Tests + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + path: 'Playwright_GH_TS' + - uses: actions/setup-node@v2 + with: + node-version: 16 + - name: Install dependencies + run: | + cd Playwright_GH_TS/search_google + npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: | + cd Playwright_GH_TS/search_google + npx playwright test + - uses: actions/upload-artifact@v2 + if: always() + with: + name: playwright-report + path: cd Playwright_GH_TS/search_google/playwright-report/ + retention-days: 90 + diff --git a/Playwright_GH_TS/...github/workflows/zive.yml b/Playwright_GH_TS/...github/workflows/zive.yml new file mode 100644 index 0000000..66aef33 --- /dev/null +++ b/Playwright_GH_TS/...github/workflows/zive.yml @@ -0,0 +1,37 @@ +name: Zive Tests + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + path: 'Playwright_GH_TS' + - uses: actions/setup-node@v2 + with: + node-version: 16 + - name: Install dependencies + run: | + cd Playwright_GH_TS/zive + npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: | + cd Playwright_GH_TS/zive + npx playwright test + - uses: actions/upload-artifact@v2 + if: always() + with: + name: playwright-report + path: cd Playwright_GH_TS/zive/playwright-report/ + retention-days: 90 + diff --git a/Playwright_GH_TS/aaa/.gitignore b/Playwright_GH_TS/aaa/.gitignore new file mode 100644 index 0000000..75e854d --- /dev/null +++ b/Playwright_GH_TS/aaa/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/Playwright_GH_TS/aaa/package-lock.json b/Playwright_GH_TS/aaa/package-lock.json new file mode 100644 index 0000000..a9226bc --- /dev/null +++ b/Playwright_GH_TS/aaa/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "aaa", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aaa", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.36.2" + } + }, + "node_modules/@playwright/test": { + "version": "1.36.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz", + "integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.36.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@types/node": { + "version": "20.4.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz", + "integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright-core": { + "version": "1.36.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz", + "integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + } + } +} diff --git a/Playwright_GH_TS/aaa/package.json b/Playwright_GH_TS/aaa/package.json new file mode 100644 index 0000000..efdc99d --- /dev/null +++ b/Playwright_GH_TS/aaa/package.json @@ -0,0 +1,13 @@ +{ + "name": "aaa", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.36.2" + } +} diff --git a/Playwright_GH_TS/aaa/playwright.config.ts b/Playwright_GH_TS/aaa/playwright.config.ts new file mode 100644 index 0000000..301801e --- /dev/null +++ b/Playwright_GH_TS/aaa/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/Playwright_GH_TS/aaa/tests-examples/demo-todo-app.spec.ts b/Playwright_GH_TS/aaa/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..2fd6016 --- /dev/null +++ b/Playwright_GH_TS/aaa/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/Playwright_GH_TS/aaa/tests/aa.spec.ts b/Playwright_GH_TS/aaa/tests/aa.spec.ts new file mode 100644 index 0000000..66af5d1 --- /dev/null +++ b/Playwright_GH_TS/aaa/tests/aa.spec.ts @@ -0,0 +1,26 @@ +const { chrome } = require('playwright'); + +(async () => { + const browser = await chrome.launch({ + headless: false + }); + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto('https://www.aaaauto.cz/'); + await page.getByRole('button', { name: 'Přijmout vše' }).click(); + await page.getByRole('button', { name: 'Značka Vyberte značku' }).click(); + await page.getByText('Škoda (2793)').first().click(); + await page.getByRole('button', { name: 'Model Vyberte model' }).click(); + await page.getByText('Citigo(40)').click(); + await page.getByRole('button', { name: 'Rok Vyberte stáří vozu' }).click(); + await page.getByText('Do 10 let').click(); + await page.getByRole('button', { name: 'Cena Vyberte cenu' }).click(); + await page.getByText('Do 200 000 Kč', { exact: true }).click(); + await page.getByRole('button', { name: 'Kategorie Vyberte kategorii' }).click(); + await page.getByText('Úsporné vozy (31)').click(); + await page.getByRole('button', { name: 'Hledat' }).click(); + + // --------------------- + await context.close(); + await browser.close(); +})(); \ No newline at end of file diff --git a/Playwright_GH_TS/aaa/tests/aaa.spec.ts b/Playwright_GH_TS/aaa/tests/aaa.spec.ts new file mode 100644 index 0000000..0ac2afc --- /dev/null +++ b/Playwright_GH_TS/aaa/tests/aaa.spec.ts @@ -0,0 +1,17 @@ +import { test, expect } from '@playwright/test'; + +test('test', async ({ page }) => { + await page.goto('https://www.aaaauto.cz/'); + await page.getByRole('button', { name: 'Přijmout vše' }).click(); + await page.getByRole('button', { name: 'Značka Vyberte značku' }).click(); + await page.getByText('Škoda', { exact: true }).first().click(); + await page.getByRole('button', { name: 'Model Vyberte model' }).click(); + await page.getByText('Citigo(40)').click(); + await page.getByRole('button', { name: 'Rok Vyberte stáří vozu' }).click(); + await page.getByText('Do 10 let').click(); + await page.getByRole('button', { name: 'Cena Vyberte cenu' }).click(); + await page.getByText('Do 200 000 Kč', { exact: true }).click(); + await page.getByRole('button', { name: 'Kategorie Vyberte kategorii' }).click(); + await page.locator('#hpFilterNG').getByText('Úsporné vozy').click(); + await page.getByRole('button', { name: 'Hledat' }).click(); +}); \ No newline at end of file diff --git a/Playwright_GH_TS/aaa/tests/example.spec.ts b/Playwright_GH_TS/aaa/tests/example.spec.ts new file mode 100644 index 0000000..c511525 --- /dev/null +++ b/Playwright_GH_TS/aaa/tests/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects the URL to contain intro. + await expect(page).toHaveURL(/.*intro/); +}); diff --git a/Playwright_GH_TS/lukan/.gitignore b/Playwright_GH_TS/lukan/.gitignore new file mode 100644 index 0000000..6d284fa --- /dev/null +++ b/Playwright_GH_TS/lukan/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/Playwright_GH_TS/lukan/e2e/example.spec.ts b/Playwright_GH_TS/lukan/e2e/example.spec.ts new file mode 100644 index 0000000..c511525 --- /dev/null +++ b/Playwright_GH_TS/lukan/e2e/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects the URL to contain intro. + await expect(page).toHaveURL(/.*intro/); +}); diff --git a/Playwright_GH_TS/lukan/package-lock.json b/Playwright_GH_TS/lukan/package-lock.json new file mode 100644 index 0000000..c41e1fc --- /dev/null +++ b/Playwright_GH_TS/lukan/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "lukan", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lukan", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.36.2" + } + }, + "node_modules/@playwright/test": { + "version": "1.36.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz", + "integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.36.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@types/node": { + "version": "20.2.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", + "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright-core": { + "version": "1.36.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz", + "integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + } + } +} diff --git a/Playwright_GH_TS/lukan/package.json b/Playwright_GH_TS/lukan/package.json new file mode 100644 index 0000000..4fe98a6 --- /dev/null +++ b/Playwright_GH_TS/lukan/package.json @@ -0,0 +1,13 @@ +{ + "name": "lukan", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.36.2" + } +} diff --git a/Playwright_GH_TS/lukan/page-objects/HomePage.ts b/Playwright_GH_TS/lukan/page-objects/HomePage.ts new file mode 100644 index 0000000..5799dc5 --- /dev/null +++ b/Playwright_GH_TS/lukan/page-objects/HomePage.ts @@ -0,0 +1,62 @@ +import { Locator, Page +} from "@playwright/test"; +export class HomePage{ + page: Page; + cookiesButtonAccept: Locator; + cookiesButtonDecline: Locator; + menuButtonUvodniStrana: Locator; + menuButtonOMne: Locator; + menuButtonZasadyOchranyOsobnichUdaju: Locator; + menuButtonPodporovatele: Locator; + searchButton: Locator; + searchFieldInput: Locator; + + constructor(page: Page){ + this.page = page; + this.cookiesButtonAccept = page.getByText('Accept'); + this.cookiesButtonDecline = page.getByText('Decline'); + this.menuButtonUvodniStrana = page.getByRole('link', { name: 'Úvodní stránka' }); + this.menuButtonOMne = page.getByRole('link' , { name: 'O Mně' }); + this.menuButtonZasadyOchranyOsobnichUdaju = page.getByRole('link' , { name: 'Zásady ochrany osobních údajů' }); + this.menuButtonPodporovatele = page.getByRole('link' , { name: 'Podporovatelé' }); + this.searchFieldInput = page.locator('//*[@id="search-7"]/form/label/input'); + this.searchButton = page.locator('#search-7 > form > button > svg > use'); + } + + async gotoHome() { + await this.page.goto('https://www.lukan.cz/'); + } + + async clickCookiesButtonAccept() { + await this.cookiesButtonAccept.click(); + } + + async clickCookiesButtonDecline() { + await this.cookiesButtonDecline.click(); + } + + async clickUvodniStranaButton() { + await this.menuButtonUvodniStrana.click(); + } + + async clickOMneButton() { + await this.menuButtonOMne.click(); + } + + async clickZOOUButton() { + await this.menuButtonZasadyOchranyOsobnichUdaju.click(); + } + + async clickPodporovateleButton() { + await this.menuButtonPodporovatele.click(); + } + async enterTextSearchFields() { + await this.searchFieldInput.fill('Test'); + } + + async clickSearchButton() { + await this.searchButton.click(); + } + + +}; \ No newline at end of file diff --git a/Playwright_GH_TS/lukan/page-objects/LoginPage.ts b/Playwright_GH_TS/lukan/page-objects/LoginPage.ts new file mode 100644 index 0000000..d4711a7 --- /dev/null +++ b/Playwright_GH_TS/lukan/page-objects/LoginPage.ts @@ -0,0 +1,5 @@ +import { Locator, Page +} from "@playwright/test"; +export class LoginPage{ + +} \ No newline at end of file diff --git a/Playwright_GH_TS/lukan/playwright.config.ts b/Playwright_GH_TS/lukan/playwright.config.ts new file mode 100644 index 0000000..34956d0 --- /dev/null +++ b/Playwright_GH_TS/lukan/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + /* + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, +*/ + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/Playwright_GH_TS/lukan/tests-examples/demo-todo-app.spec.ts b/Playwright_GH_TS/lukan/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..2fd6016 --- /dev/null +++ b/Playwright_GH_TS/lukan/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/Playwright_GH_TS/lukan/tests/cookies.test.spec.ts b/Playwright_GH_TS/lukan/tests/cookies.test.spec.ts new file mode 100644 index 0000000..aaf84da --- /dev/null +++ b/Playwright_GH_TS/lukan/tests/cookies.test.spec.ts @@ -0,0 +1,19 @@ +import { test, expect } from '@playwright/test'; +import { HomePage } from '../page-objects/HomePage'; + + +// test odsouhlasení cookies +test('HomePage click Accept cookies', async ({ page }) => { + + const homePage = new HomePage(page); + await homePage.gotoHome(); + await homePage.clickCookiesButtonAccept(); + }); + + + // test zamítnutí cookies +test('HomaPage click Decline cookies' , async ({page}) => { + const homePage = new HomePage(page); + await homePage.gotoHome(); + await homePage.clickCookiesButtonDecline(); +}) \ No newline at end of file diff --git a/Playwright_GH_TS/lukan/tests/example.spec.ts b/Playwright_GH_TS/lukan/tests/example.spec.ts new file mode 100644 index 0000000..c511525 --- /dev/null +++ b/Playwright_GH_TS/lukan/tests/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects the URL to contain intro. + await expect(page).toHaveURL(/.*intro/); +}); diff --git a/Playwright_GH_TS/lukan/tests/login.spec.ts b/Playwright_GH_TS/lukan/tests/login.spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/Playwright_GH_TS/lukan/tests/menuButton.test.spec.ts b/Playwright_GH_TS/lukan/tests/menuButton.test.spec.ts new file mode 100644 index 0000000..d4cb240 --- /dev/null +++ b/Playwright_GH_TS/lukan/tests/menuButton.test.spec.ts @@ -0,0 +1,14 @@ +import { test, expect } from '@playwright/test'; +import { HomePage } from '../page-objects/HomePage'; + +test('Click menu button with HomePage' , async ({ page }) => { + const homePage = new HomePage(page); + await homePage.gotoHome(); + await homePage.clickUvodniStranaButton(); + await homePage.clickOMneButton(); + await homePage.clickZOOUButton(); + await homePage.clickPodporovateleButton(); + + //await context.close(); + //await browser.close(); +}); \ No newline at end of file diff --git a/Playwright_GH_TS/lukan/tests/search.spec.ts b/Playwright_GH_TS/lukan/tests/search.spec.ts new file mode 100644 index 0000000..0f781be --- /dev/null +++ b/Playwright_GH_TS/lukan/tests/search.spec.ts @@ -0,0 +1,10 @@ +import { test, expect } from '@playwright/test'; +import { HomePage } from '../page-objects/HomePage'; + +test('Search web' , async ({ page }) => { + const homePage = new HomePage(page); + await homePage.gotoHome(); + await homePage.enterTextSearchFields(); + await homePage.clickSearchButton(); + +}); \ No newline at end of file diff --git a/Playwright_GH_TS/lukan/tests/test-1.spec.ts b/Playwright_GH_TS/lukan/tests/test-1.spec.ts new file mode 100644 index 0000000..29fdff8 --- /dev/null +++ b/Playwright_GH_TS/lukan/tests/test-1.spec.ts @@ -0,0 +1,6 @@ +import { test, expect } from '@playwright/test'; + +test('test', async ({ page }) => { + await page.goto('https://lukan.cz/'); + await page.getByRole('link', { name: 'Úvodní stránka' }).click(); +}); \ No newline at end of file diff --git a/Playwright_GH_TS/search_google/.gitignore b/Playwright_GH_TS/search_google/.gitignore new file mode 100644 index 0000000..40a2227 --- /dev/null +++ b/Playwright_GH_TS/search_google/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ +/test-results/ +/playwright-report/ +/playwright/.cache/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/Playwright_GH_TS/search_google/package-lock.json b/Playwright_GH_TS/search_google/package-lock.json new file mode 100644 index 0000000..f8e68b1 --- /dev/null +++ b/Playwright_GH_TS/search_google/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "search_google", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "search_google", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.31.2" + } + }, + "node_modules/@playwright/test": { + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.31.2.tgz", + "integrity": "sha512-BYVutxDI4JeZKV1+ups6dt5WiqKhjBtIYowyZIJ3kBDmJgsuPKsqqKNIMFbUePLSCmp2cZu+BDL427RcNKTRYw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.31.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@types/node": { + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright-core": { + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.2.tgz", + "integrity": "sha512-a1dFgCNQw4vCsG7bnojZjDnPewZcw7tZUNFN0ZkcLYKj+mPmXvg4MpaaKZ5SgqPsOmqIf2YsVRkgqiRDxD+fDQ==", + "dev": true, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + } + } +} diff --git a/Playwright_GH_TS/search_google/package.json b/Playwright_GH_TS/search_google/package.json new file mode 100644 index 0000000..7240edc --- /dev/null +++ b/Playwright_GH_TS/search_google/package.json @@ -0,0 +1,13 @@ +{ + "name": "search_google", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.31.2" + } +} diff --git a/Playwright_GH_TS/search_google/playwright.config.ts b/Playwright_GH_TS/search_google/playwright.config.ts new file mode 100644 index 0000000..213b45f --- /dev/null +++ b/Playwright_GH_TS/search_google/playwright.config.ts @@ -0,0 +1,90 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000 + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + /* + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + */ + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { channel: 'chrome' }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, +}); diff --git a/Playwright_GH_TS/search_google/tests-examples/demo-todo-app.spec.ts b/Playwright_GH_TS/search_google/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..2fd6016 --- /dev/null +++ b/Playwright_GH_TS/search_google/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/Playwright_GH_TS/search_google/tests/example.spec.ts b/Playwright_GH_TS/search_google/tests/example.spec.ts new file mode 100644 index 0000000..bd681a2 --- /dev/null +++ b/Playwright_GH_TS/search_google/tests/example.spec.ts @@ -0,0 +1,33 @@ +//****Tento projekt je TypeScript****// + +import { test, expect } from '@playwright/test'; +import { chromium } from '@playwright/test'; + +test('has title', async ({ page }) => { + // const browser = await chromium.launch(); + // const context = await browser.newContext(); + await page.goto('https://www.google.com'); + + // Odsouhlasí cookies + await page.click("#W0wltc > div"); + + // Najdeme pole vyhledávání a napíšeme do něj slovo "test" + const searchInput = await page.$('[name="q"]'); + await searchInput?.type('test'); + + // Potvrdíme vyhledávání stisknutím klávesy Enter + await searchInput?.press('Enter'); + + // Nebo můžeme kliknout na tlačítko pro vyhledávání + // const searchButton = await page.$('[name="btnK"]'); + // await searchButton?.click(); + + // Vypíše název stránky do konzole + const title = await page.title(); + console.log(title); + + //await page.waitForNavigation(); + // console.log('Search results page title:', await page.title()); + + // await browser.close(); +}); diff --git a/Playwright_GH_TS/zive.ts/.gitignore b/Playwright_GH_TS/zive.ts/.gitignore new file mode 100644 index 0000000..75e854d --- /dev/null +++ b/Playwright_GH_TS/zive.ts/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/Playwright_GH_TS/zive.ts/package-lock.json b/Playwright_GH_TS/zive.ts/package-lock.json new file mode 100644 index 0000000..dc57de5 --- /dev/null +++ b/Playwright_GH_TS/zive.ts/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "zive.ts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "zive.ts", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.31.2" + } + }, + "node_modules/@playwright/test": { + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.31.2.tgz", + "integrity": "sha512-BYVutxDI4JeZKV1+ups6dt5WiqKhjBtIYowyZIJ3kBDmJgsuPKsqqKNIMFbUePLSCmp2cZu+BDL427RcNKTRYw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.31.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@types/node": { + "version": "18.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", + "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright-core": { + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.2.tgz", + "integrity": "sha512-a1dFgCNQw4vCsG7bnojZjDnPewZcw7tZUNFN0ZkcLYKj+mPmXvg4MpaaKZ5SgqPsOmqIf2YsVRkgqiRDxD+fDQ==", + "dev": true, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + } + } +} diff --git a/Playwright_GH_TS/zive.ts/package.json b/Playwright_GH_TS/zive.ts/package.json new file mode 100644 index 0000000..82cccf6 --- /dev/null +++ b/Playwright_GH_TS/zive.ts/package.json @@ -0,0 +1,13 @@ +{ + "name": "zive.ts", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.31.2" + } +} diff --git a/Playwright_GH_TS/zive.ts/playwright.config.ts b/Playwright_GH_TS/zive.ts/playwright.config.ts new file mode 100644 index 0000000..5e5154b --- /dev/null +++ b/Playwright_GH_TS/zive.ts/playwright.config.ts @@ -0,0 +1,90 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000 + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { channel: 'chrome' }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, +}); diff --git a/Playwright_GH_TS/zive.ts/tests-examples/demo-todo-app.spec.ts b/Playwright_GH_TS/zive.ts/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..2fd6016 --- /dev/null +++ b/Playwright_GH_TS/zive.ts/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/Playwright_GH_TS/zive.ts/tests/example.spec.ts b/Playwright_GH_TS/zive.ts/tests/example.spec.ts new file mode 100644 index 0000000..ecbe392 --- /dev/null +++ b/Playwright_GH_TS/zive.ts/tests/example.spec.ts @@ -0,0 +1,11 @@ +import { test, expect } from '@playwright/test'; + +test('test', async ({ page }) => { + await page.goto('https://www.zive.cz/'); + await page.getByRole('button', { name: 'Souhlasit a zavřít: Souhlasit s naším zpracováním údajů a zavřít' }).click(); + await page.getByRole('link', { name: 'Menu' }).click(); + await page.getByPlaceholder('Hledat na Živě').click(); + await page.getByPlaceholder('Hledat na Živě').fill('zive'); + await page.goto('https://www.zive.cz/vysledky-vyhledavani/sc-236/?q=zive'); + await page.getByRole('link', { name: 'O počítačích, IT a internetu - Živě.cz' }).click(); +}); \ No newline at end of file diff --git a/Playwright_GH_TS/zive/.gitignore b/Playwright_GH_TS/zive/.gitignore new file mode 100644 index 0000000..6d284fa --- /dev/null +++ b/Playwright_GH_TS/zive/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/Playwright_GH_TS/zive/e2e/example.spec.ts b/Playwright_GH_TS/zive/e2e/example.spec.ts new file mode 100644 index 0000000..c511525 --- /dev/null +++ b/Playwright_GH_TS/zive/e2e/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects the URL to contain intro. + await expect(page).toHaveURL(/.*intro/); +}); diff --git a/Playwright_GH_TS/zive/package-lock.json b/Playwright_GH_TS/zive/package-lock.json new file mode 100644 index 0000000..df8ded4 --- /dev/null +++ b/Playwright_GH_TS/zive/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "zive", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "zive", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.31.2" + } + }, + "node_modules/@playwright/test": { + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.31.2.tgz", + "integrity": "sha512-BYVutxDI4JeZKV1+ups6dt5WiqKhjBtIYowyZIJ3kBDmJgsuPKsqqKNIMFbUePLSCmp2cZu+BDL427RcNKTRYw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.31.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@types/node": { + "version": "18.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", + "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright-core": { + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.2.tgz", + "integrity": "sha512-a1dFgCNQw4vCsG7bnojZjDnPewZcw7tZUNFN0ZkcLYKj+mPmXvg4MpaaKZ5SgqPsOmqIf2YsVRkgqiRDxD+fDQ==", + "dev": true, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + } + } +} diff --git a/Playwright_GH_TS/zive/package.json b/Playwright_GH_TS/zive/package.json new file mode 100644 index 0000000..caf7bbc --- /dev/null +++ b/Playwright_GH_TS/zive/package.json @@ -0,0 +1,16 @@ +{ + "name": "zive", + "version": "1.0.0", + "description": "", + "main": "index.js", + "directories": { + "test": "tests" + }, + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.31.2" + } +} diff --git a/Playwright_GH_TS/zive/playwright.config.ts b/Playwright_GH_TS/zive/playwright.config.ts new file mode 100644 index 0000000..fbf188a --- /dev/null +++ b/Playwright_GH_TS/zive/playwright.config.ts @@ -0,0 +1,90 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './e2e', + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000 + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { channel: 'chrome' }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, +}); diff --git a/Playwright_GH_TS/zive/tests-examples/demo-todo-app.spec.ts b/Playwright_GH_TS/zive/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..2fd6016 --- /dev/null +++ b/Playwright_GH_TS/zive/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/Playwright_GH_TS/zive/tests/example.spec.ts b/Playwright_GH_TS/zive/tests/example.spec.ts new file mode 100644 index 0000000..3914e7f --- /dev/null +++ b/Playwright_GH_TS/zive/tests/example.spec.ts @@ -0,0 +1,44 @@ +import { test, expect } from '@playwright/test'; +import { chromium } from '@playwright/test'; + + + test('has title', async ({ page }) => { + + await page.goto('https://zive.cz'); + + // Odsouhlasí cookies + await page.click("//button[@id='didomi-notice-agree-button']/span"); + + // takto + //const button = await page.locator("//button[@id='didomi-notice-agree-button']"); + //await button.click(); + + // Klikne na menu + await page.click("//a[contains(text(),'Menu')]"); + + // vrátí se na hlavní stranu + await page.click("#mainFORM > nav > div > div.header > a.mn-logo"); + + // klikne do vyhledávání + await page.click("//a[@onclick='layout.menu.toggle(true)']"); + + // klikne do vyhledávacího pole + const searchInput = await page.$("#mainFORM > nav > div > div.header > div"); + await searchInput?.type('test'); + + // Potvrdíme vyhledávání stisknutím klávesy Enter + await searchInput?.press('Enter'); + + // vrátíme se na hlavní stránku + await page.click('//*[@id="mainFORM"]/div[3]/header/div[2]/div/a'); + + // Zkontrolujeme, že stránka má správný název + const pageTitle = await page.title(); + expect(pageTitle).toBe('Živě.cz – O počítačích, internetu, vědě a technice'); + + + + + + + });