Skip to content

Commit

Permalink
Feat: upgrade e2e testing (openemr#7766)
Browse files Browse the repository at this point in the history
Now tests:
- new patient and open patient
- new encounter and open encounter
- all menu items (both main menu including Popups and user menu) and including menu items that require an open patient and open encounter
  • Loading branch information
bradymiller authored Oct 14, 2024
1 parent 8493cde commit 2a8405b
Show file tree
Hide file tree
Showing 33 changed files with 1,372 additions and 318 deletions.
14 changes: 2 additions & 12 deletions ci/ciLibrary.source
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,11 @@ build_test_unit() {
}

build_test_e2e() {
# Since e2e testing is a bit inconsistent, gonna do up to three runs (and only one needs to pass to pass the e2e testing)
# first run
failTest=false
docker exec -i $(docker ps | grep openemr[_\-] | cut -f 1 -d " ") sh -c "${CHROMIUM_INSTALL}; export PANTHER_NO_SANDBOX=1; export PANTHER_CHROME_ARGUMENTS='--disable-dev-shm-usage'; cd ${OPENEMR_DIR}; php ${OPENEMR_DIR}/vendor/bin/phpunit --testsuite e2e --testdox" || failTest=true
if [ "$failTest" == false ]; then
exit
fi
# try run again
failTest=false
docker exec -i $(docker ps | grep openemr[_\-] | cut -f 1 -d " ") sh -c "${CHROMIUM_INSTALL}; export PANTHER_NO_SANDBOX=1; export PANTHER_CHROME_ARGUMENTS='--disable-dev-shm-usage'; cd ${OPENEMR_DIR}; php ${OPENEMR_DIR}/vendor/bin/phpunit --testsuite e2e --testdox" || failTest=true
if [ "$failTest" == false ]; then
exit
if $failTest; then
exit 1
fi
# failed 2 tests, so send the official fail
exit 1
}

build_test_api() {
Expand Down
2 changes: 1 addition & 1 deletion interface/forms/newpatient/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ function cancelClickedOld() {
<div class="btn-group" role="group">
<?php $link_submit = ($viewmode || empty($_GET['autoloaded'])) ? '' : 'link_submit';
$cancel_clicked = ($viewmode) ? 'cancelClickedOld()' : 'cancelClickedNew()';?>
<button type="button" class="btn btn-primary btn-save" onclick="top.restoreSession(); saveClicked(undefined);"><?php echo xlt('Save'); ?></button>
<button type="button" id="saveEncounter" class="btn btn-primary btn-save" onclick="top.restoreSession(); saveClicked(undefined);"><?php echo xlt('Save'); ?></button>
<button type="button" class="btn btn-cancel <?php echo $link_submit;?>" onClick="return <?php echo $cancel_clicked;?>"><?php echo xlt('Cancel'); ?></button>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion interface/new/new_search_popup.php
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ function submitList(offset) {
<?php if ($pubpid_matched) { ?>
<input class='btn btn-primary' type='button' value='<?php echo xla('Cancel'); ?>' onclick='dlgclose();' />
<?php } else { ?>
<button class='btn btn-primary my-1' type='button' value='true' onclick='dlgclose("srcConfirmSave", false);'><?php echo xla('Confirm Create New Patient'); ?></button>
<button id='confirmCreate' class='btn btn-primary my-1' type='button' value='true' onclick='dlgclose("srcConfirmSave", false);'><?php echo xla('Confirm Create New Patient'); ?></button>
<button class='btn btn-primary my-1' type='button' value='Cancel' onclick='dlgclose();'><?php echo xla('Cancel'); ?></button>
<?php } ?>
</div>
Expand Down
2 changes: 1 addition & 1 deletion templates/encounter/forms/navbar.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<span class="navbar-toggler-icon"></span>
</button>

<span class="navbar-brand">
<span id="navbarEncounterTitle" class="navbar-brand">
{{ encounterDate|text }} {{ (groupEncounter == true) ? "Group Encounter"|xlt : "Encounter"|xlt}} {{ "for"|xlt }} {{ patientName|text }}
</span>

Expand Down
25 changes: 13 additions & 12 deletions tests/Tests/E2e/LoginTest.php → tests/Tests/E2e/AaLoginTest.php
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
<?php

/**
* LoginTest class
* AaLoginTest class
*
* @package OpenEMR
* @link https://www.open-emr.org
* @auther zerai
* @author Dixon Whitmire
* @author Brady Miller <[email protected]>
* @package OpenEMR
* @link https://www.open-emr.org
* @auther zerai
* @author Dixon Whitmire
* @author Brady Miller <[email protected]>
* @copyright Copyright (c) 2019 zerai
* @copyright Copyright (c) 2020 Dixon Whitmire
* @copyright Copyright (c) 2024 Brady Miller <[email protected]>
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
*/

declare(strict_types=1);

namespace OpenEMR\Tests\E2e;

use OpenEMR\Tests\E2e\Base\BaseTrait;
use OpenEMR\Tests\E2e\Login\LoginTestData;
use OpenEMR\Tests\E2e\Login\LoginTrait;
use Symfony\Component\Panther\PantherTestCase;
use Symfony\Component\Panther\Client;

class LoginTest extends PantherTestCase
class AaLoginTest extends PantherTestCase
{
use BaseTrait;
use LoginTrait;

protected $client;
protected $crawler;
private $client;
private $crawler;

public function testGoToOpenemrLoginPage(): void
{
Expand All @@ -50,7 +51,7 @@ public function testLoginUnauthorized(): void
{
$this->base();
try {
$this->login('admin', 'pass1', false);
$this->login(LoginTestData::username, LoginTestData::password . "1", false);
} catch (\Throwable $e) {
// Close client
$this->client->quit();
Expand Down Expand Up @@ -79,7 +80,7 @@ public function testurlWithoutTokenShouldRedirectToLoginPage(): void
$this->client->quit();
}

protected function loginPage(): void
private function loginPage(): void
{
$this->crawler = $this->client->request('GET', '/interface/login/login.php?site=default');
$title = $this->client->getTitle();
Expand Down
139 changes: 129 additions & 10 deletions tests/Tests/E2e/Base/BaseTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,163 @@
/**
* BaseTrait trait
*
* @package OpenEMR
* @link https://www.open-emr.org
* @author Brady Miller <[email protected]>
* @package OpenEMR
* @link https://www.open-emr.org
* @author Brady Miller <[email protected]>
* @copyright Copyright (c) 2024 Brady Miller <[email protected]>
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
*/

declare(strict_types=1);

namespace OpenEMR\Tests\E2e\Base;

use Facebook\WebDriver\WebDriverBy;
use OpenEMR\Tests\E2e\Xpaths\XpathsConstants;

trait BaseTrait
{
protected function base(): void
private function base(): void
{
$e2eBaseUrl = getenv("OPENEMR_BASE_URL_E2E", true) ?: "http://localhost";
$this->client = static::createPantherClient(['external_base_uri' => $e2eBaseUrl]);
$this->client->manage()->window()->maximize();
}

protected function switchToIFrame($selector): void
private function switchToIFrame(string $xpath): void
{
$selector = WebDriverBy::xpath($xpath);
$iframe = $this->client->findElement($selector);
$this->client->switchTo()->frame($iframe);
$this->crawler = $this->client->refreshCrawler();
}

protected function assertActiveTab($text): void
private function assertActiveTab(string $text, string $loading = "Loading", bool $looseTabTitle = false, bool $clearAlert = false): void
{
if ($clearAlert) {
// ok the alert (example case of this is when open the Create Visit link since there is already an encounter on same day)
$this->client->wait(10)->until(function ($driver) {
try {
$alert = $driver->switchTo()->alert();
$alert->accept();
return true; // Alert is present and has been cleared
} catch (\Exception $e) {
return false; // Alert is not present
}
});
}
$startTime = (int) (microtime(true) * 1000);
while (strpos($this->crawler->filterXPath(XpathsConstants::ACTIVE_TAB)->text(), "Loading") === 0) {
if (str_contains($loading, "||")) {
// have 2 $loading to check
$loading = explode("||", $loading);
while (
str_contains($this->crawler->filterXPath(XpathsConstants::ACTIVE_TAB)->text(), $loading[0]) ||
str_contains($this->crawler->filterXPath(XpathsConstants::ACTIVE_TAB)->text(), $loading[1])
) {
if (($startTime + 10000) < ((int)(microtime(true) * 1000))) {
$this->fail("Timeout waiting for tab [$text]");
}
usleep(100);
}
} else {
// only have 1 $loading to check
while (str_contains($this->crawler->filterXPath(XpathsConstants::ACTIVE_TAB)->text(), $loading)) {
if (($startTime + 10000) < ((int)(microtime(true) * 1000))) {
$this->fail("Timeout waiting for tab [$text]");
}
usleep(100);
}
}
if ($looseTabTitle) {
$this->assertTrue(str_contains($this->crawler->filterXPath(XpathsConstants::ACTIVE_TAB)->text(), $text), "[$text] tab load FAILED");
} else {
$this->assertSame($text, $this->crawler->filterXPath(XpathsConstants::ACTIVE_TAB)->text(), "[$text] tab load FAILED");
}
}

private function assertActivePopup(string $text): void
{
$this->client->waitFor(XpathsConstants::MODAL_TITLE);
$this->crawler = $this->client->refreshCrawler();
$startTime = (int) (microtime(true) * 1000);
while (empty($this->crawler->filterXPath(XpathsConstants::MODAL_TITLE)->text())) {
if (($startTime + 10000) < ((int) (microtime(true) * 1000))) {
$this->fail("Timeout waiting for tab [$text]");
$this->fail("Timeout waiting for popup [$text]");
}
usleep(100);
}
$this->assertSame($text, $this->crawler->filterXPath(XpathsConstants::ACTIVE_TAB)->text(), "[$text] tab load FAILED");
$this->assertSame($text, $this->crawler->filterXPath(XpathsConstants::MODAL_TITLE)->text(), "[$text] popup load FAILED");
}

private function goToMainMenuLink(string $menuLink): void
{
// ensure on main page (ie. not in an iframe)
$this->client->switchTo()->defaultContent();
// got to and click the menu link
$menuLinkSequenceArray = explode('||', $menuLink);
$counter = 0;
foreach ($menuLinkSequenceArray as $menuLinkItem) {
if ($counter == 0) {
if (count($menuLinkSequenceArray) > 1) {
// start clicking through a dropdown/nested menu item
$menuLink = '//div[@id="mainMenu"]/div/div/div/div[text()="' . $menuLinkItem . '"]';
} else {
// just clicking a simple/single menu item
$menuLink = '//div[@id="mainMenu"]/div/div/div[text()="' . $menuLinkItem . '"]';
}
} elseif ($counter == 1) {
if (count($menuLinkSequenceArray) == 2) {
// click the nested menu item
$menuLink = '//div[@id="mainMenu"]/div/div/div/div[text()="' . $menuLinkSequenceArray[0] . '"]/../ul/li/div[text()="' . $menuLinkItem . '"]';
} else {
// continue clicking through a dropdown/nested menu item
$menuLink = '//div[@id="mainMenu"]/div/div/div/div[text()="' . $menuLinkSequenceArray[0] . '"]/../ul/li/div/div[text()="' . $menuLinkItem . '"]';
}
} else { // $counter > 1
// click the nested menu item
$menuLink = '//div[@id="mainMenu"]/div/div/div/div[text()="' . $menuLinkSequenceArray[0] . '"]/../ul/li/div/div[text()="' . $menuLinkSequenceArray[1] . '"]/../ul/li/div[text()="' . $menuLinkItem . '"]';
}

$this->client->waitFor($menuLink);
$this->crawler = $this->client->refreshCrawler();
$this->crawler->filterXPath($menuLink)->click();
$counter++;
}
}

private function goToUserMenuLink(string $menuTreeIcon): void
{
$menuLink = XpathsConstants::USER_MENU_ICON;
$menuLink2 = '//ul[@id="userdropdown"]//i[contains(@class, "' . $menuTreeIcon . '")]';
$this->client->waitFor($menuLink);
$this->crawler = $this->client->refreshCrawler();
$this->crawler->filterXPath($menuLink)->click();
$this->client->waitFor($menuLink2);
$this->crawler = $this->client->refreshCrawler();
$this->crawler->filterXPath($menuLink2)->click();
}

private function isPatientExist(string $firstname, string $lastname, string $dob, string $sex): bool
{
$patientDatabase = sqlQuery("SELECT `fname` FROM `patient_data` WHERE `fname` = ? AND `lname` = ? AND `DOB` = ? AND `sex` = ?", [$firstname, $lastname, $dob, $sex]);
if (!empty($patientDatabase['fname']) && ($patientDatabase['fname'] == $firstname)) {
return true;
} else {
return false;
}
}

private function isEncounterExist(string $firstname, string $lastname, string $dob, string $sex): bool
{
$patientDatabase = sqlQuery("SELECT `patient_data`.`fname`
FROM `patient_data`
INNER JOIN `form_encounter`
ON `patient_data`.`pid` = `form_encounter`.`pid`
WHERE `patient_data`.`fname` = ? AND `patient_data`.`lname` = ? AND `patient_data`.`DOB` = ? AND `patient_data`.`sex` = ?", [$firstname, $lastname, $dob, $sex]);
if (!empty($patientDatabase['fname']) && ($patientDatabase['fname'] == $firstname)) {
return true;
} else {
return false;
}
}
}
54 changes: 54 additions & 0 deletions tests/Tests/E2e/BbCreateStaffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/**
* BbCreateStaffTest class
*
* @package OpenEMR
* @link https://www.open-emr.org
* @auther Bartosz Spyrko-Smietanko
* @author Brady Miller <[email protected]>
* @copyright Copyright (c) 2020 Bartosz Spyrko-Smietanko
* @copyright Copyright (c) 2024 Brady Miller <[email protected]>
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
*/

declare(strict_types=1);

namespace OpenEMR\Tests\E2e;

use OpenEMR\Tests\E2e\Base\BaseTrait;
use OpenEMR\Tests\E2e\Login\LoginTrait;
use OpenEMR\Tests\E2e\User\UserAddTrait;
use Symfony\Component\Panther\PantherTestCase;
use Symfony\Component\Panther\Client;

class BbCreateStaffTest extends PantherTestCase
{
use BaseTrait;
use LoginTrait;
use UserAddTrait;

private $client;
private $crawler;

protected function setUp(): void
{
// clean up in case still left over from prior testing
$this->cleanDatabase();
}

protected function tearDown(): void
{
$this->cleanDatabase();
}

private function cleanDatabase(): void
{
// remove the created user
$delete = "DELETE FROM users WHERE username = ?";
sqlStatement($delete, array('foobar'));

$delete = "DELETE FROM users_secure WHERE username = ?";
sqlStatement($delete, array('foobar'));
}
}
31 changes: 31 additions & 0 deletions tests/Tests/E2e/CcCreatePatientTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/**
* CcCreatePatientTest class
*
* @package OpenEMR
* @link https://www.open-emr.org
* @author Brady Miller <[email protected]>
* @copyright Copyright (c) 2024 Brady Miller <[email protected]>
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
*/

declare(strict_types=1);

namespace OpenEMR\Tests\E2e;

use OpenEMR\Tests\E2e\Base\BaseTrait;
use OpenEMR\Tests\E2e\Login\LoginTrait;
use OpenEMR\Tests\E2e\Patient\PatientAddTrait;
use Symfony\Component\Panther\PantherTestCase;
use Symfony\Component\Panther\Client;

class CcCreatePatientTest extends PantherTestCase
{
use BaseTrait;
use LoginTrait;
use PatientAddTrait;

private $client;
private $crawler;
}
Loading

0 comments on commit 2a8405b

Please sign in to comment.