Refactor for E2E

This commit is contained in:
An Phan 2016-11-24 11:56:00 +08:00
parent 46266409d5
commit 46a52a55a6
No known key found for this signature in database
GPG key ID: 05536BB4BCDC02A2
10 changed files with 261 additions and 144 deletions

View file

@ -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');

View file

@ -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"]');
}
}

View file

@ -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');

View file

@ -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)');
}
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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)) {

View file

@ -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');
}
}

View file

@ -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;
}
}

View file

@ -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');
}
}