From 46a52a55a6147658b2a8eb2eb32e92429ce16359 Mon Sep 17 00:00:00 2001 From: An Phan Date: Thu, 24 Nov 2016 11:56:00 +0800 Subject: [PATCH] Refactor for E2E --- tests/e2e/PlaylistScreenTest.php | 5 +- tests/e2e/ProfileScreenTest.php | 40 ++++----- tests/e2e/QueueScreenTest.php | 4 +- tests/e2e/SideBarTest.php | 36 ++++---- tests/e2e/SongListActions.php | 10 +-- tests/e2e/SongListTest.php | 82 +++++++---------- tests/e2e/TestCase.php | 28 +++++- tests/e2e/UsersScreenTest.php | 35 ++++---- tests/e2e/WebDriverShortcuts.php | 141 +++++++++++++++++++++++++++--- tests/e2e/ZSettingsScreenTest.php | 24 ++--- 10 files changed, 261 insertions(+), 144 deletions(-) diff --git a/tests/e2e/PlaylistScreenTest.php b/tests/e2e/PlaylistScreenTest.php index 9ea9d933..23e85164 100644 --- a/tests/e2e/PlaylistScreenTest.php +++ b/tests/e2e/PlaylistScreenTest.php @@ -10,9 +10,8 @@ class PlaylistScreenTest extends TestCase { $this->loginAndGoTo('songs') ->selectRange() - ->createPlaylist('Bar'); - - $this->seeText('Bar', '#playlists > ul'); + ->createPlaylist('Bar') + ->seeText('Bar', '#playlists > ul'); $this->click('#sidebar .playlist:nth-last-child(1)'); $this->see('#playlistWrapper'); diff --git a/tests/e2e/ProfileScreenTest.php b/tests/e2e/ProfileScreenTest.php index 8bd3bebd..26fd2b35 100644 --- a/tests/e2e/ProfileScreenTest.php +++ b/tests/e2e/ProfileScreenTest.php @@ -9,17 +9,17 @@ class ProfileScreenTest extends TestCase { public function testProfileScreen() { - $this->loginAndWait(); - $this->click('a.view-profile'); - $this->see('#profileWrapper'); - // Now we change some user profile details - $this->typeIn('#profileWrapper input[name="name"]', 'Mr Bar'); - $this->typeIn('#profileWrapper input[name="email"]', 'bar@koel.net'); - $this->enter(); - $this->see('.sweet-alert'); - // Dismiss the alert first - $this->press(WebDriverKeys::ESCAPE); - $this->notSee('.sweet-alert'); + $this->loginAndWait() + ->click('a.view-profile'); + $this->see('#profileWrapper') + // Now we change some user profile details + ->typeIn('#profileWrapper input[name="name"]', 'Mr Bar') + ->typeIn('#profileWrapper input[name="email"]', 'bar@koel.net') + ->enter() + ->see('.sweet-alert') + // Dismiss the alert first + ->press(WebDriverKeys::ESCAPE) + ->notSee('.sweet-alert'); $avatar = $this->el('a.view-profile img'); // Expect the Gravatar to be updated @@ -27,17 +27,17 @@ class ProfileScreenTest extends TestCase // Check "Confirm Closing" and validate its functionality $this->click('#profileWrapper input[name="confirmClosing"]'); - $this->refresh(); - $this->waitUntil(WebDriverExpectedCondition::alertIsPresent()); + $this->refresh() + ->waitUntil(WebDriverExpectedCondition::alertIsPresent()); $this->driver->switchTo()->alert()->dismiss(); // Reverse all changes for other tests to not be affected - $this->typeIn('#profileWrapper input[name="name"]', 'Koel Admin'); - $this->typeIn('#profileWrapper input[name="email"]', 'koel@example.com'); - $this->enter(); - $this->see('.sweet-alert'); - $this->press(WebDriverKeys::ESCAPE); - $this->notSee('.sweet-alert'); - $this->click('#profileWrapper input[name="confirmClosing"]'); + $this->typeIn('#profileWrapper input[name="name"]', 'Koel Admin') + ->typeIn('#profileWrapper input[name="email"]', 'koel@example.com') + ->enter() + ->see('.sweet-alert') + ->press(WebDriverKeys::ESCAPE) + ->notSee('.sweet-alert') + ->click('#profileWrapper input[name="confirmClosing"]'); } } diff --git a/tests/e2e/QueueScreenTest.php b/tests/e2e/QueueScreenTest.php index 161d891f..517ed0bd 100644 --- a/tests/e2e/QueueScreenTest.php +++ b/tests/e2e/QueueScreenTest.php @@ -11,9 +11,7 @@ class QueueScreenTest extends TestCase // As the queue is currently empty, the "Shuffling all song" link should be there $this->click('#queueWrapper a.start'); - $this->waitUntil(function () { - return count($this->els('#queueWrapper .song-item')); - }); + $this->see('#queueWrapper .song-item'); // Clear the queue $this->click('#queueWrapper .buttons button.btn-clear-queue'); diff --git a/tests/e2e/SideBarTest.php b/tests/e2e/SideBarTest.php index 2a827f0c..f3b14315 100644 --- a/tests/e2e/SideBarTest.php +++ b/tests/e2e/SideBarTest.php @@ -10,34 +10,32 @@ class SideBarTest extends TestCase // All basic navigation foreach (['home', 'queue', 'songs', 'albums', 'artists', 'youtube', 'settings', 'users'] as $screen) { - $this->goto($screen); - $this->waitUntil(function () use ($screen) { - return $this->driver->getCurrentURL() === $this->url.'/#!/'.$screen; - }); + $this->goto($screen) + ->waitUntil(function () use ($screen) { + return $this->driver->getCurrentURL() === $this->url.'/#!/'.$screen; + }); } // Add a playlist $this->click('#playlists > h1 > i.create'); - $this->see('#playlists > form.create'); - $this->typeIn('#playlists > form > input[type="text"]', 'Bar'); - $this->enter(); - $this->waitUntil(function () { - $list = $this->els('#playlists .playlist'); - - return end($list)->getText() === 'Bar'; - }); + $this->see('#playlists > form.create') + ->typeIn('#playlists > form > input[type="text"]', 'Bar') + ->enter() + ->waitUntil(function () { + $list = $this->els('#playlists .playlist'); + return end($list)->getText() === 'Bar'; + }); // Double click to edit/rename a playlist - $this->doubleClick('#playlists .playlist:nth-child(2)'); - $this->see('#playlists .playlist:nth-child(2) input[type="text"]'); - $this->typeIn('#playlists .playlist:nth-child(2) input[type="text"]', 'Qux'); - $this->enter(); - $this->seeText('Qux', '#playlists .playlist:nth-child(2)'); + $this->doubleClick('#playlists .playlist:nth-child(2)') + ->see('#playlists .playlist:nth-child(2) input[type="text"]') + ->typeIn('#playlists .playlist:nth-child(2) input[type="text"]', 'Qux') + ->enter() + ->seeText('Qux', '#playlists .playlist:nth-child(2)'); // Edit with an empty name shouldn't do anything. $this->doubleClick('#playlists .playlist:nth-child(2)'); $this->click('#playlists .playlist:nth-child(2) input[type="text"]')->clear(); - $this->enter(); - $this->seeText('Qux', '#playlists .playlist:nth-child(2)'); + $this->enter()->seeText('Qux', '#playlists .playlist:nth-child(2)'); } } diff --git a/tests/e2e/SongListActions.php b/tests/e2e/SongListActions.php index 139747eb..f222d4e4 100644 --- a/tests/e2e/SongListActions.php +++ b/tests/e2e/SongListActions.php @@ -55,13 +55,11 @@ trait SongListActions */ public function cmdSelectSongs() { - $actions = (new WebDriverActions($this->driver)) - ->keyDown(null, WebDriverKeys::COMMAND); + $actions = (new WebDriverActions($this->driver))->keyDown(null, WebDriverKeys::COMMAND); foreach (func_get_args() as $i) { $actions->click($this->el("{$this->wrapperId} tr.song-item:nth-child($i)")); } - $actions->keyUp(null, WebDriverKeys::COMMAND) - ->perform(); + $actions->keyUp(null, WebDriverKeys::COMMAND)->perform(); return $this; } @@ -77,8 +75,8 @@ trait SongListActions { // Try adding a song into a new playlist $this->click("{$this->wrapperId} .buttons button.btn-add-to"); - $this->typeIn("{$this->wrapperId} .buttons input[type='text']", $playlistName); - $this->enter(); + $this->typeIn("{$this->wrapperId} .buttons input[type='text']", $playlistName) + ->enter(); return $this; } diff --git a/tests/e2e/SongListTest.php b/tests/e2e/SongListTest.php index 3cc1b24a..3224992f 100644 --- a/tests/e2e/SongListTest.php +++ b/tests/e2e/SongListTest.php @@ -4,7 +4,6 @@ namespace E2E; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverElement; -use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverKeys; class SongListTest extends TestCase @@ -38,8 +37,7 @@ class SongListTest extends TestCase ); // Delete key should remove selected songs - $this->press(WebDriverKeys::DELETE); - $this->waitUntil(function () { + $this->press(WebDriverKeys::DELETE)->waitUntil(function () { return count($this->els('#queueWrapper tr.song-item.selected')) === 0 && count($this->els('#queueWrapper tr.song-item')) === 7; }); @@ -51,18 +49,13 @@ class SongListTest extends TestCase public function testActionButtons() { - $this->loginAndWait()->repopulateList(); - - // Since no songs are selected, the "Shuffle All" button must be shown - $this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated( - WebDriverBy::cssSelector('#queueWrapper button.btn-shuffle-all') - )); - - // Now we selected all songs for the "Shuffle Selected" button to be shown - $this->selectAllSongs(); - $this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated( - WebDriverBy::cssSelector('#queueWrapper button.btn-shuffle-selected') - )); + $this->loginAndWait() + ->repopulateList() + // Since no songs are selected, the "Shuffle All" button must be shown + ->see('#queueWrapper button.btn-shuffle-all') + // Now we selected all songs for the "Shuffle Selected" button to be shown + ->selectAllSongs() + ->see('#queueWrapper button.btn-shuffle-selected'); // Add to favorites $this->selectSong(); @@ -71,13 +64,11 @@ class SongListTest extends TestCase $this->goto('favorites'); static::assertCount(1, $this->els('#favoritesWrapper tr.song-item')); - $this->goto('queue'); - $this->selectSong(); + $this->goto('queue') + ->selectSong(); // Try adding a song into a new playlist - $this->createPlaylist('Foo'); - $this->waitUntil(WebDriverExpectedCondition::textToBePresentInElement( - WebDriverBy::cssSelector('#playlists > ul'), 'Foo' - )); + $this->createPlaylist('Foo') + ->seeText('Foo', '#playlists > ul'); } public function testSorting() @@ -97,8 +88,8 @@ class SongListTest extends TestCase } // Now go to All Songs screen and sort there - $this->goto('songs'); - $this->click('#songsWrapper div.song-list-wrap th:nth-child(2)'); + $this->goto('songs') + ->click('#songsWrapper div.song-list-wrap th:nth-child(2)'); $last = null; $results = []; /** @var WebDriverElement $td */ @@ -124,43 +115,34 @@ class SongListTest extends TestCase public function testContextMenu() { - $this->loginAndGoTo('songs'); - $this->rightClickOnSong(); - - $by = WebDriverBy::cssSelector('#songsWrapper .song-menu'); - $this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated($by)); + $this->loginAndGoTo('songs') + ->rightClickOnSong() + ->see('#songsWrapper .song-menu'); // 7 sub menu items static::assertCount(7, $this->els('#songsWrapper .song-menu > li')); // Clicking the "Go to Album" menu item $this->click('#songsWrapper .song-menu > li:nth-child(2)'); - $this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated( - WebDriverBy::cssSelector('#albumWrapper') - )); + $this->see('#albumWrapper'); // Clicking the "Go to Artist" menu item - $this->back(); - $this->rightClickOnSong(); - $this->click('#songsWrapper .song-menu > li:nth-child(3)'); - $this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated( - WebDriverBy::cssSelector('#artistWrapper') - )); + $this->back() + ->rightClickOnSong() + ->click('#songsWrapper .song-menu > li:nth-child(3)'); + $this->see('#artistWrapper'); // Clicking "Edit" - $this->back(); - $this->rightClickOnSong(); - $this->click('#songsWrapper .song-menu > li:nth-child(5)'); - $this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated( - WebDriverBy::cssSelector('#editSongsOverlay form') - )); + $this->back() + ->rightClickOnSong() + ->click('#songsWrapper .song-menu > li:nth-child(5)'); + $this->see('#editSongsOverlay form'); + // Updating song - $this->typeIn('#editSongsOverlay form input[name="title"]', 'Foo'); - $this->typeIn('#editSongsOverlay form input[name="track"]', 99); - $this->enter(); - $this->waitUntil(WebDriverExpectedCondition::invisibilityOfElementLocated( - WebDriverBy::cssSelector('#editSongsOverlay form') - )); + $this->typeIn('#editSongsOverlay form input[name="title"]', 'Foo') + ->typeIn('#editSongsOverlay form input[name="track"]', 99) + ->enter() + ->notSee('#editSongsOverlay form'); static::assertEquals('99', $this->el('#songsWrapper tr.song-item:nth-child(1) .track-number')->getText()); static::assertEquals('Foo', $this->el('#songsWrapper tr.song-item:nth-child(1) .title')->getText()); } @@ -171,5 +153,7 @@ class SongListTest extends TestCase $this->goto('albums'); $this->click('#albumsWrapper > div > article:nth-child(1) .meta a.shuffle-album'); $this->goto('queue'); + + return $this; } } diff --git a/tests/e2e/TestCase.php b/tests/e2e/TestCase.php index 425a4035..07d5a898 100644 --- a/tests/e2e/TestCase.php +++ b/tests/e2e/TestCase.php @@ -58,6 +58,9 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase return $this->app; } + /** + * Reset the test data for E2E tests. + */ protected function resetData() { // Make sure we have a fresh database. @@ -90,16 +93,23 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase return $this; } + /** + * Log in and wait for the app to finish loading. + * + * @return $this + * + * @throws \Exception + */ protected function loginAndWait() { - $this->login(); - $this->seeText('Koel Admin', '#userBadge > a.view-profile'); + $this->login() + ->seeText('Koel Admin', '#userBadge > a.view-profile'); return $this; } /** - * A helper to allow going to a specific screen. + * Go to a specific screen. * * @param $screen * @@ -122,11 +132,23 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase return $this; } + /** + * Log in and go to a specific screen. + * + * @param $screen string + * + * @return $this + * + * @throws \Exception + */ protected function loginAndGoTo($screen) { return $this->loginAndWait()->goto($screen); } + /** + * Wait for the user to press ENTER key before continuing. + */ protected function waitForUserInput() { if (trim(fgets(fopen('php://stdin', 'rb'))) !== chr(13)) { diff --git a/tests/e2e/UsersScreenTest.php b/tests/e2e/UsersScreenTest.php index f249065a..15a418d8 100644 --- a/tests/e2e/UsersScreenTest.php +++ b/tests/e2e/UsersScreenTest.php @@ -8,26 +8,25 @@ class UsersScreenTest extends TestCase { public function testUsersScreen() { - $this->loginAndGoTo('users'); - $this->see('.user-item.me'); + $this->loginAndGoTo('users')->see('.user-item.me'); // Hover to the first user item (new WebDriverMouseMoveAction($this->driver->getMouse(), $this->el('#usersWrapper .user-item.me'))) ->perform(); // and validate that the button reads "Update Profile" instead of "Edit" - static::assertContains('Update Profile', $this->el('#usersWrapper .user-item.me .btn-edit')->getText()); + static::assertContains('Update Profile', + $this->el('#usersWrapper .user-item.me .btn-edit')->getText()); // Also, clicking it should show the "Profile & Preferences" panel $this->click('#usersWrapper .user-item.me .btn-edit'); - $this->see('#profileWrapper'); - $this->back(); - - // Add new user - $this->click('#usersWrapper .btn-add'); - $this->see('form.user-create'); - $this->typeIn('form.user-create input[name="name"]', 'Foo'); - $this->typeIn('form.user-create input[name="email"]', 'foo@koel.net'); - $this->typeIn('form.user-create input[name="password"]', 'SecureMuch'); - $this->enter(); - $this->seeText('foo@koel.net', '#usersWrapper'); + $this->see('#profileWrapper') + ->back() + // Add new user + ->click('#usersWrapper .btn-add'); + $this->see('form.user-create') + ->typeIn('form.user-create input[name="name"]', 'Foo') + ->typeIn('form.user-create input[name="email"]', 'foo@koel.net') + ->typeIn('form.user-create input[name="password"]', 'SecureMuch') + ->enter() + ->seeText('foo@koel.net', '#usersWrapper'); // Hover the next user item (not me) (new WebDriverMouseMoveAction($this->driver->getMouse(), $this->el('#usersWrapper .user-item:not(.me)'))) @@ -35,9 +34,9 @@ class UsersScreenTest extends TestCase static::assertContains('Edit', $this->el('#usersWrapper .user-item:not(.me) .btn-edit')->getText()); // Edit user $this->click('#usersWrapper .user-item:not(.me) .btn-edit'); - $this->see('form.user-edit'); - $this->typeIn('form.user-edit input[name="email"]', 'bar@koel.net'); - $this->enter(); - $this->seeText('bar@koel.net', '#usersWrapper'); + $this->see('form.user-edit') + ->typeIn('form.user-edit input[name="email"]', 'bar@koel.net') + ->enter() + ->seeText('bar@koel.net', '#usersWrapper'); } } diff --git a/tests/e2e/WebDriverShortcuts.php b/tests/e2e/WebDriverShortcuts.php index 4218c900..44070626 100644 --- a/tests/e2e/WebDriverShortcuts.php +++ b/tests/e2e/WebDriverShortcuts.php @@ -30,16 +30,40 @@ trait WebDriverShortcuts return $selector; } + /** + * Get a list of elements by a selector. + * + * @param $selector + * + * @return \Facebook\WebDriver\Remote\RemoteWebElement[] + */ protected function els($selector) { return $this->driver->findElements(WebDriverBy::cssSelector($selector)); } + /** + * Type a string. + * + * @param $string + * + * @return $this + */ protected function type($string) { - return $this->driver->getKeyboard()->sendKeys($string); + $this->driver->getKeyboard()->sendKeys($string); + + return $this; } + /** + * Type into an element. + * + * @param $element + * @param $string + * + * @return $this + */ protected function typeIn($element, $string) { $this->click($element)->clear(); @@ -47,41 +71,80 @@ trait WebDriverShortcuts return $this->type($string); } + /** + * Press a key. + * + * @param string $key + * + * @return $this + */ protected function press($key = WebDriverKeys::ENTER) { - return $this->driver->getKeyboard()->pressKey($key); + $this->driver->getKeyboard()->pressKey($key); + + return $this; } + /** + * Press enter. + * + * @return $this + */ protected function enter() { return $this->press(); } + /** + * Click an element. + * + * @param $element + * + * @return WebDriverElement + */ protected function click($element) { return $this->el($element)->click(); } + /** + * Right-click an element. + * + * @param $element + * + * @return \Facebook\WebDriver\Remote\RemoteMouse + */ protected function rightClick($element) { return $this->driver->getMouse()->contextClick($this->el($element)->getCoordinates()); } + /** + * Double-click and element. + * + * @param $element + * + * @return $this + */ protected function doubleClick($element) { - $action = new WebDriverDoubleClickAction($this->driver->getMouse(), $this->el($element)); + (new WebDriverDoubleClickAction($this->driver->getMouse(), $this->el($element)))->perform(); - $action->perform(); + return $this; } /** * Sleep (implicit wait) for some seconds. * * @param $seconds + * + * @return $this */ protected function sleep($seconds) { $this->driver->manage()->timeouts()->implicitlyWait($seconds); + + return $this; } /** @@ -92,46 +155,102 @@ trait WebDriverShortcuts * * @throws \Exception * - * @return mixed + * @return $this */ protected function waitUntil($func, $timeout = 10) { - return $this->driver->wait($timeout)->until($func); + $this->driver->wait($timeout)->until($func); + + return $this; } + /** + * Wait and validate an element to be visible. + * + * @param $selector + * + * @return $this + * + * @throws \Exception + */ public function see($selector) { - return $this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated( + $this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated( WebDriverBy::cssSelector($selector) )); + + return $this; } + /** + * Wait and validate an element to be invisible. + * + * @param $selector string The element's CSS selector. + * + * @return $this + * + * @throws \Exception + */ public function notSee($selector) { - return $this->waitUntil(WebDriverExpectedCondition::invisibilityOfElementLocated( + $this->waitUntil(WebDriverExpectedCondition::invisibilityOfElementLocated( WebDriverBy::cssSelector($selector) )); + + return $this; } + /** + * Wait and validate a text to be visible in an element. + * + * @param $text + * @param $selector string The element's CSS selector. + * + * @return $this + * @throws \Exception + */ public function seeText($text, $selector) { $this->waitUntil(WebDriverExpectedCondition::textToBePresentInElement( WebDriverBy::cssSelector($selector), $text )); + + return $this; } + /** + * Navigate back. + * + * @return $this + */ protected function back() { - return $this->driver->navigate()->back(); + $this->driver->navigate()->back(); + + return $this; } + /** + * Navigate forward. + * + * @return $this + */ protected function forward() { - return $this->driver->navigate()->forward(); + $this->driver->navigate()->forward(); + + return $this; } + /** + * Refresh the page. + * + * @return $this + */ protected function refresh() { - return $this->driver->navigate()->refresh(); + $this->driver->navigate()->refresh(); + + return $this; } } diff --git a/tests/e2e/ZSettingsScreenTest.php b/tests/e2e/ZSettingsScreenTest.php index 789d81f0..ca28bf4d 100644 --- a/tests/e2e/ZSettingsScreenTest.php +++ b/tests/e2e/ZSettingsScreenTest.php @@ -11,17 +11,17 @@ class ZSettingsScreenTest extends TestCase { public function testSettingsScreen() { - $this->loginAndGoTo('settings'); - $this->typeIn('#inputSettingsPath', dirname(__DIR__.'/../songs')); - $this->enter(); - // Wait for the page to reload - $this->waitUntil(function () { - return $this->driver->executeScript('return document.readyState') === 'complete'; - }); - // And for the loading screen to disappear - $this->notSee('#overlay'); - $this->goto('albums'); - // and make sure the scanning is good. - $this->seeText('Koel Testing Vol', '#albumsWrapper'); + $this->loginAndGoTo('settings') + ->typeIn('#inputSettingsPath', dirname(__DIR__.'/../songs')) + ->enter() + // Wait for the page to reload + ->waitUntil(function () { + return $this->driver->executeScript('return document.readyState') === 'complete'; + }) + // And for the loading screen to disappear + ->notSee('#overlay') + ->goto('albums') + // and make sure the scanning is good. + ->seeText('Koel Testing Vol', '#albumsWrapper'); } }