We offer trusted digital content, adaptive algorithms, and teaching support tools to make it easier for all teachers to differentiate their instruction so every child performs better.
Lead developer
Senior Developer
Senior developer
Developer (Main QA)
// If the app_environment is null, use the environment variable
if ($environment == null) {
if (file_exists(REAL_ROOT_DIR . '/.testing')) {
$environment = env('LB_TESTING_ENV', 'default,local,testing');
} else {
$environment = env('LB_ENV', 'default');
}
}
window.confirm = function (confirmText) {
var result = lb.testing.confirm.acceptConfirm;
lb.testing.confirm.active = true;
lb.testing.confirm.text = confirmText;
lb.testing.confirm.acceptConfirm = true; //Restore defaults after a call
return result;
};
window.alert = function (alertText) {
lb.testing.alert.active = true;
lb.testing.alert.text = alertText;
return true;
};
Then I should see "confirm" dialog with message
"Are you sure you want to delete the playlist: 'TESTING PLAYLIST'?"
and resolve it
window.onerror = function(error, url, line) {
lb.testing.errors.push(error + ". Line: " + line);
};
$numErrors = $this->getSession()->evaluateScript('typeof(lb) != "undefined"
? lb.testing.errors.length : 0');
$(document).on('pjax:start', function () {
if (lb.testing.pjax.enabled && lb.testing.pjax.activeCount == undefined) {
lb.testing.pjax.activeCount = 0;
}
lb.testing.pjax.activeCount++;
});
$(document).on('pjax:end', function () {
if (lb.testing.pjax.enabled) {
lb.testing.pjax.activeCount--;
}
});
$(document).ajaxSend(function (event, jqXHR, ajaxOptions) {
lb.testing.ajax.activeCount++;
});
$(document).ajaxComplete(function (event, jqXHR, ajaxOptions) {
lb.testing.ajax.activeCount--;
lb.testing.ajax.latestRequest = (new Date()).getTime();
});
And I wait for ajax calls to complete
Then I should be brought to page matching "/search\?community=1"
/**
* @Then I should be brought to page matching :regex
* @Then I should be brought to page matching :regex within :timeout seconds
*/
public function shouldBeBroughtToPageRegex($regex, $timeout = self::DEFAULT_TIMEOUT)
{
$this->getSession()->wait(
$timeout * 1000,
"new RegExp('" . $this->locatePath($regex) . "', 'i')
.test(window.location) == true"
);
\Assert\Assertion::regex($this->getSession()->getCurrentUrl(),
'|' . $this->locatePath($regex) . '|i');
}
Given I am on "/"
When X item is clicked
Then I should be on "/search?community=1"
$countEqFunc = function () use ($element, $numberOfItems) {
if (count($element->findAll('css', '.selectables-container button'))
== $numberOfItems) {
return true;
}
return false;
};
if (!$element->waitFor(self::DEFAULT_TIMEOUT, $countEqFunc)) {
throw new ExpectationException("message");
}
public function waitFor($timeout, $callback)
{
$start = microtime(true);
$end = $start + $timeout;
do {
$result = call_user_func($callback, $this);
if ($result) {
break;
}
usleep(100000);
} while (microtime(true) < $end);
return $result;
}
When ActionsButton ".actions-widget" is clicked
Then ActionsButton ".actions-widget" should contain "Edit" action
trait ActionsButtonTrait
{
/**
* @When ActionsButton :selector is clicked
*/
public function actionsButtonClick($selector = null);
/**
* @Then ActionsButton :selector should contain :text action
*/
public function actionsButtonAssertHasAction($text, $selector = null);
/**
* @Then ActionsButton :selector should not contain :text action
*/
public function actionsButtonAssertDoesNotHaveAction($text, $selector = null);
And I am logged in as "testing+teacher@learningbird.com"
public function loginAsUser($username)
{
$this->visit('/signIn');
$this->fillField('signInEmailAddressField', $username);
$this->fillField('signInPasswordField', 'testing-password');
$this->pressButton('signInTrigger');
$this->userShouldBeLoggedIn();
}
public function userShouldBeLoggedIn()
{
try {
$this->elementShouldAppear('css', '#navbar', null, 25);
} catch (ExpectationException $ex) {
$this->assertPageNotContainsText('Could not verify your login. Please make sure you entered your details correctly.');
throw $ex;
}
}
(Part 2 of readability)
Then I should see "Create Quiz"
And BaseList ".quizzes-dashboard .base-list" should
contain "1" item
And BaseList ".quizzes-dashboard .base-list" item
":nth-of-type(1)" data "numberOfAssignments" should contain "0"
And BaseList ".quizzes-dashboard .base-list" item
":nth-of-type(1)" data "numberOfQuestions" should contain "5"
And BaseList ".quizzes-dashboard .base-list" item
":nth-of-type(1)" data "commands" should contain a "ActionsButton" widget
And ActionsButton ".quizzes-dashboard .base-list:nth-of-type(1) .actions-button"
should not contain "Edit" action
And ActionsButton ".quizzes-dashboard .base-list:nth-of-type(1) .actions-button"
should not contain "Assign" action
When ActionsButton ".quizzes-dashboard .base-list:nth-of-type(1) .actions-button"
is clicked
And BaseList ".quizzes-dashboard .base-list" should contain "1" item
And BaseList item ":nth-of-type(1)" data "numberOfAssignments" should contain "0"
And BaseList item data "numberOfQuestions" should contain "5"
And BaseList item data "commands" should contain a "ActionsButton" widget
And ActionsButton should contain "Edit" action
When ActionsButton action "Edit" is clicked
//Save the current element as "BaseList"
return $this->getWidgetElement($listSelector, 'BaseList');
protected function getWidgetElement($selector, $widgetName, $inElement = null)
{
if ($selector == null) {
$element = $this->getWidgetContext($widgetName);
return $element;
} else {
$element = $this->getElement('css', $selector, $inElement);
Assertion::true(
$element->hasClass($expectedClass = $this->getWidgetClass($widgetName)),
'Element under "' . $selector . '" found but class "' . $expectedClass . '" not found'
);
$this->setWidgetContext($widgetName, $element);
return $element;
}
}
And teacher "teacher@testing.com" has the following classes:
| className | gradeLevel | subject |
| Math class | 9 | Math |
| Science class | 10 | Science |
/**
* @Given teacher :teacherEmail has the following classes:
*/
public function createClassesForTeacher(TableNode $classData, string $teacherEmail): array //[classesRecord]
{
$teacher = $this->getOrCreateTeacher($teacherEmail);
$account = $teacher->getAccount();
if ((int)$account->getAccountTypeID() !== accountTypesModel::TEACHER) {
throw new Exception('referenced teacher must be a rogue teacher');
}
$classes = $this->createClassesFromTableNode($classData, $account);
foreach ($classes as $class) {
$this->associateTeacherToClass($teacher, $class);
}
return $classes;
}
Given a skeleton "teacher" with the email "teacher@testing.com" exists
Given a skeleton "teacher" with the email "teacher2@testing.com" and
password "testpwd" exists with 60 licenses
/**
* @Given a skeleton :userType with the email :emailAccount and password :password exists
* with :numLicenses licenses
*/
public function createSkeletonUserAccount($emailAccount, $userType,
$password = self::DEFAULT_PASSWORD, $numLicenses = 100)
{
switch ($userType) {
case 'teacher':
$this->createSkeletonTeacher($emailAccount, $password, $numLicenses);
break;
case 'student':
$this->createSkeletonStudent($emailAccount, $password);
break;
case 'school_admin':
$this->createSkeletonSchoolAdmin($emailAccount, $password);
break;
}
}
/**
* @BeforeSuite
*
* @param \Behat\Testwork\Hook\Scope\BeforeSuiteScope $scope
*/
public static function beforeSuite(\Behat\Testwork\Hook\Scope\BeforeSuiteScope $scope)
{
$suite = $scope->getSuite();
file_put_contents(REAL_ROOT_DIR . '/.testing', \Carbon\Carbon::now()->format('Y-m-d G:i:s'));
if (!file_exists(LOGS_DIR . "/behat-screenshots/{$suite->getName()}/")) {
mkdir(LOGS_DIR . "/behat-screenshots/{$suite->getName()}/", 0755, true);
}
// Kill the screenshots
/** @var SplFileInfo $file */
foreach (new DirectoryIterator(LOGS_DIR . "/behat-screenshots/{$suite->getName()}/") as $file) {
if (substr($file->getFilename(), 0, 1) == '.') {
continue;
}
echo 'Removing ' . realpath($file->getPathName()) . PHP_EOL;
unlink($file->getPathName());
}
self::printSuiteInfo($suite);
}
/**
* @BeforeScenario
*
* @param \Behat\Behat\Hook\Scope\BeforeScenarioScope $scope
*/
public function before(\Behat\Behat\Hook\Scope\BeforeScenarioScope $scope)
{
$this->cleanDatabase();
$this->resetWidgetContexts();
$this->getSession()->resizeWindow(1440, 900, 'current');
#Cookies cause issues when executing/evaluating scripts in
#internet explorer
$this->getSession()->getDriver()->reset();
}
/**
* @AfterStep
*/
public function afterStepTasks(\Behat\Behat\Hook\Scope\AfterStepScope $event)
{
// Failed step
if ($event->getTestResult()->getResultCode() ==
\Behat\Testwork\Tester\Result\TestResult::FAILED) {
$this->takeScreenshotAfterFailedStep($event->getStep()->getText(),
$event->getSuite());
}
// Collect and handle javascript errors
$this->reportJavascriptErrors();
}
/**
* @AfterScenario
*
* @param \Behat\Behat\Hook\Scope\AfterScenarioScope $scope
*/
public function after(\Behat\Behat\Hook\Scope\AfterScenarioScope $scope)
{
// Make sure you can exit without getting asked if you want to leave
$this->getSession()->executeScript('window.onbeforeunload = null;');
$this->clearMailFromMemory();
$this->cleanPlaylists();
}
/**
* @AfterSuite
*
* @param \Behat\Testwork\Hook\Scope\AfterSuiteScope $scope
*/
public static function afterSuite(\Behat\Testwork\Hook\Scope\AfterSuiteScope $scope)
{
unlink(REAL_ROOT_DIR . '/.testing');
}
Senior Dev @ Learning Bird
Twitter: @thecrazycodr
LinkedIn: @crazycoders
GitHub: @crazycodr
Email: thecrazycodr@gmail.com
Open source contributions
Contact me
Standard-Exceptions 2.0: GitHub