all actions and tests inside a story are executed through an instance of this class, making this class the StoryTeller :)
Author: Stuart Herbert (stuart.herbert@datasift.com)
 public function initAfterModulesAvailable(StoryTeller $st, CliEngine $engine, Injectables $injectables)
 {
     // are we persisting the device?
     if (isset($engine->options->persistProcesses) && $engine->options->persistProcesses) {
         $st->setPersistProcesses();
     }
 }
 public function initAfterModulesAvailable(StoryTeller $st, CliEngine $engine, Injectables $injectables)
 {
     // shorthand
     $output = $injectables->output;
     // are we keeping the test environment hanging around afterwards?
     if (isset($engine->options->persistTarget) && $engine->options->persistTarget) {
         $injectables->activeConfig->setData('storyplayer.phases.testEnvShutdown.TestEnvironmentDestruction', false);
         $injectables->activeConfig->unsetData('storyplayer.phases.userAbort.TestEnvironmentDestruction');
         $st->setPersistTestEnvironment();
     }
     // are we trying to use a test environment that has previously
     // been persisted?
     if (isset($engine->options->reuseTarget) && $engine->options->reuseTarget) {
         // does the target exist to be reused?
         $output->setSilentMode();
         $hasTarget = $st->fromTargetsTable()->hasCurrentTestEnvironment();
         $output->resetSilentMode();
         if (!$hasTarget) {
             $output->logCliWarning("target environment '" . $st->getTestEnvironmentName() . "' does not exist; ignoring --reuse-target switch");
             return;
         }
         // okay, so we have the test environment in the targets table,
         // but has the test environment been changed at all?
         $output->setSilentMode();
         $origSig = $st->fromTargetsTable()->getCurrentTestEnvironmentSignature();
         $currentSig = $st->getTestEnvironmentSignature();
         $output->resetSilentMode();
         if ($origSig != $currentSig) {
             // our test environment entry isn't valid, so remove it
             $output->setSilentMode();
             $st->usingTargetsTable()->removeCurrentTestEnvironment();
             $output->resetSilentMode();
             $output->logCliWarning("target environment '" . $st->getTestEnvironmentName() . "' has changed; ignoring --reuse-target switch");
             return;
         }
         // if we get here, then we do not need to create the test environment
         $injectables->activeConfig->setData('storyplayer.phases.testEnvStartup.TestEnvironmentConstruction', false);
     } else {
         // do we already have this target?
         //
         // this can happen when the test environment was previously
         // persisted, and this time we're being run without the
         // --reuse-target flag
         // does the target exist to be reused?
         $output->setSilentMode();
         $hasTarget = $st->fromTargetsTable()->hasCurrentTestEnvironment();
         $output->resetSilentMode();
         if ($hasTarget) {
             // remove this target from the table
             $output->setSilentMode();
             $st->usingTargetsTable()->removeCurrentTestEnvironment();
             $output->resetSilentMode();
         }
     }
 }
 /**
  *
  * @param  StoryTeller $st
  * @return void
  */
 public function start(StoryTeller $st)
 {
     // Sauce Labs handles proxying for us (if required)
     // via the Sauce Connect app
     // build the Sauce Labs url
     $url = "http://" . urlencode($this->browserDetails->saucelabs->username) . ':' . urlencode($this->browserDetails->saucelabs->accesskey) . '@ondemand.saucelabs.com/wd/hub';
     // build the Sauce Labs capabilities array
     $desiredCapabilities = $this->browserDetails->desiredCapabilities;
     // add the story's name, so that someone looking at the Sauce Labs
     // list of jobs can see what this browser was used for
     //
     // due to encoding errors at SauceLabs, we can't use '>' as a
     // delimiter in the story's name
     $story = $st->getStory();
     $desiredCapabilities['name'] = $st->getTestEnvironmentName() . ' / ' . $st->getCurrentPhase() . ': ' . $st->getCurrentPhaseName() . ' / ' . $story->getName();
     // create the browser session
     $webDriver = new WebDriverClient($url);
     $this->browserSession = $webDriver->newSession($this->browserDetails->browser, $desiredCapabilities);
 }
Esempio n. 4
0
 public function play(StoryTeller $st, Injectables $injectables)
 {
     // shorthand
     $output = $st->getOutput();
     // we're building / destroying a test environment
     $activity = 'Creating test environment';
     $testEnv = new TestEnvironment($injectables->activeTestEnvironmentName);
     // we're using this to build / destroy our test environment
     $phasesPlayer = new PhaseGroup_Player();
     // announce what we're doing
     $output->startPhaseGroup($activity, $injectables->activeTestEnvironmentName);
     // run the startup phase
     $phasesPlayer->playPhases($activity, $st, $injectables, $this->startupPhases, $testEnv);
     $creationResult = $testEnv->getResult();
     $output->endPhaseGroup($creationResult);
     // what happened?
     if (!$creationResult->getPhaseGroupSucceeded() && !$creationResult->getPhaseGroupSkipped()) {
         $output->logCliError("failed to create test environment - cannot continue");
         exit(1);
     }
     // now we need to play whatever runs against this
     // test environment
     foreach ($this->wrappedPlayers as $wrappedPlayer) {
         // play the wrapped item
         //
         // this is normally a story
         $wrappedPlayer->play($st, $injectables);
     }
     // announce what we're doing
     $activity = 'Destroying test environment';
     $output->startPhaseGroup($activity, $injectables->activeTestEnvironmentName);
     // run the shutdown phase
     $testEnv->resetResult();
     $phasesPlayer->playPhases($activity, $st, $injectables, $this->shutdownPhases, $testEnv);
     $output->endPhaseGroup($testEnv->getResult());
     // all done
 }
 /**
  * @param string $type
  */
 public function runHandlers(StoryTeller $st, $type)
 {
     // shorthand
     $output = $st->getOutput();
     // Do we have any persistent tables to cleanup?
     $runtimeConfig = $st->getRuntimeConfig();
     if (!isset($runtimeConfig->storyplayer, $runtimeConfig->storyplayer->tables)) {
         // there are no tables at all
         return;
     }
     // if we get here, then we know that there are persistent
     // tables that we need to cleanup
     // this will keep track of any tables that have no associated
     // cleanup handler
     $missingCleanupHandlers = [];
     // Take a look at all of our process list tables
     foreach ($runtimeConfig->storyplayer->tables as $key => $value) {
         $className = "cleanup" . ucfirst($key);
         try {
             $st->{$className}($key)->{$type}();
         } catch (E5xx_NoMatchingActions $e) {
             // We don't know about a cleanup module for this, SHOUT LOUDLY
             $missingCleanupHandlers[] = "Missing cleanup module for '{$key}'" . PHP_EOL;
         }
     }
     // Now we've cleaned everything up, save the runtime config
     $st->saveRuntimeConfig();
     // If we have any missing cleanup handlers, output it to the screen
     // and exit with an error code
     if (count($missingCleanupHandlers)) {
         foreach ($missingCleanupHandlers as $msg) {
             $output->logCliError($msg);
         }
         exit(1);
     }
 }
Esempio n. 6
0
 protected function initDevice()
 {
     // start the test device
     $this->device = $this->st->getRunningDevice();
     // set our top XPATH node
     //
     // for the moment, we are assuming that the test device is
     // a web browser, because historically this has always been
     // the case
     //
     // when this assumption is no longer valid, we will need to
     // revisit this code
     $this->setTopXpath("//html");
     // set our top element
     //
     // we cannot assume that the browser has any DOM loaded at all
     $this->setTopElement($this->device);
 }
Esempio n. 7
0
 public function uploadFile($sourceFilename, $destFilename)
 {
     // vet our input
     Contract::RequiresValue($sourceFilename, is_string($sourceFilename));
     Contract::RequiresValue($sourceFilename, !empty($sourceFilename));
     Contract::RequiresValue($sourceFilename, is_file($sourceFilename));
     Contract::RequiresValue($destFilename, is_string($destFilename));
     Contract::RequiresValue($destFilename, !empty($destFilename));
     // make the params printable / executable
     // $printableParams = $this->convertParamsForUse($params);
     // what are we doing?
     $log = usingLog()->startAction("upload file '{$sourceFilename}' to host as '{$destFilename}'");
     // do we actually have everything we need to run the command?
     if (!$this->hasSshUsername()) {
         throw new E4xx_NeedSshUsername();
     }
     if (!$this->hasIpAddress()) {
         throw new E4xx_NeedIpAddress();
     }
     // build the full command
     //
     // the options that we pass (by default) to SCP:
     //
     // -o StrictHostKeyChecking=no
     //    do not verify the SSH host key (avoids an interactive prompt)
     // -i <private_key>
     //    use specified private key to access the remote/guest OS
     // <username>@<hostname>
     //    who we are logging in as (should never be root)
     // <additional SSH options>
     //    any other flags, such as -n to force non-interactive session
     $fullCommand = 'scp ' . ' ' . $this->getSshKeyForUse() . ' ' . $this->getScpOptionsForUse() . ' "' . $sourceFilename . '" ' . ' "' . $this->getSshUsername() . '@' . $this->getIpAddress() . ':' . $destFilename . '"';
     // run the command
     $commandRunner = $this->st->getNewCommandRunner();
     $result = $commandRunner->runSilently($fullCommand);
     // all done
     $log->endAction("return code was '{$result->returnCode}'");
     return $result;
 }
Esempio n. 8
0
 public function uploadFile($sourceFilename, $destFilename)
 {
     // vet our input
     Contract::RequiresValue($sourceFilename, is_string($sourceFilename));
     Contract::RequiresValue($sourceFilename, !empty($sourceFilename));
     Contract::RequiresValue($sourceFilename, is_file($sourceFilename));
     Contract::RequiresValue($destFilename, is_string($destFilename));
     Contract::RequiresValue($destFilename, !empty($destFilename));
     // make the params printable / executable
     // $printableParams = $this->convertParamsForUse($params);
     // what are we doing?
     $log = usingLog()->startAction("copy file '{$sourceFilename}' to localhost as '{$destFilename}'");
     // build the full command
     //
     $fullCommand = 'cp ' . "'" . $sourceFilename . "' " . "'" . $destFilename . "'";
     // run the command
     $commandRunner = $this->st->getNewCommandRunner();
     $result = $commandRunner->runSilently($fullCommand);
     // all done
     $log->endAction("return code was '{$result->returnCode}'");
     return $result;
 }
Esempio n. 9
0
 /**
  *
  * @param  StoryTeller $st
  * @param  Injectables $injectables
  * @param  Phase       $phase
  * @param  boolean     $isActive
  * @return Phase_Result
  */
 public function playPhase(StoryTeller $st, Injectables $injectables, Phase $phase, $isActive, $thingBeingPlayed = null)
 {
     // shorthand
     $output = $st->getOutput();
     $phaseName = $phase->getPhaseName();
     // run the phase if we're allowed to
     if ($isActive) {
         $st->setCurrentPhase($phase);
         $phaseResult = $phase->doPhase($thingBeingPlayed);
     } else {
         $phaseResult = new Phase_Result($phaseName);
         $phaseResult->setContinuePlaying($phaseResult::SKIPPED);
         $output->logPhaseSkipped($phaseName, self::MSG_PHASE_NOT_ACTIVE);
     }
     // close off any open log actions
     $st->closeAllOpenActions();
     // stop any running test devices
     if (!$st->getPersistDevice()) {
         $st->stopDevice();
     }
     // close off any log actions left open by closing down
     // the test device
     $st->closeAllOpenActions();
     // all done
     return $phaseResult;
 }
Esempio n. 10
0
 public function play(StoryTeller $st, Injectables $injectables)
 {
     // shorthand
     $output = $st->getOutput();
     // we're going to use this to play our setup and teardown phases
     $phasesPlayer = new PhaseGroup_Player();
     // load our story
     $story = Story_Loader::loadStory($this->storyFilename);
     $st->setStory($story);
     // does our story want to keep the test device open between
     // phases?
     if ($story->getPersistDevice()) {
         $st->setPersistDevice();
     }
     // set default callbacks up
     $story->setDefaultCallbacks();
     // make sure we start with a brand new checkpoint
     $st->setCheckpoint(new Story_Checkpoint($st));
     // tell the outside world what we're doing
     $activity = "Running story";
     $name = $story->getCategory() . ' > ' . $story->getGroupAsString() . ' > ' . $story->getName();
     $output->startPhaseGroup($activity, $name);
     // run the phases before the story truly starts
     $phasesPlayer->playPhases($activity, $st, $injectables, $this->startupPhases, $story);
     // what happened?
     $result = $story->getResult();
     if (!$result->getPhaseGroupSucceeded()) {
         // make sure the result has the story's filename in
         $result->filename = $this->storyFilename;
         // announce the results
         $output->endPhaseGroup($result);
         // all done
         return;
     }
     // run the phases in the 'story' section
     $phasesPlayer->playPhases($activity, $st, $injectables, $this->storyPhases, $story);
     // grab the result at this point
     $result = clone $story->getResult();
     // run the shutdown phase
     $phasesPlayer->playPhases($activity, $st, $injectables, $this->shutdownPhases, $story);
     // do we also need to look at any failures that happened during
     // the shutdown phase?
     if ($result->getPhaseGroupSucceeded()) {
         $result = $story->getResult();
     }
     // make sure the result has the story's filename in
     $result->filename = $this->storyFilename;
     // announce the results
     $output->endPhaseGroup($result);
     // all done
 }
Esempio n. 11
0
 /**
  *
  * @param  VagrantVmDetails $vmDetails
  * @param  array $provisioningVars
  * @return void
  */
 public function createHost($vmDetails, $provisioningVars = array())
 {
     // what are we doing?
     $log = usingLog()->startAction('provision new VM');
     // make sure we like the provided details
     foreach (array('name', 'osName', 'homeFolder') as $param) {
         if (!isset($vmDetails->{$param})) {
             throw new E5xx_ActionFailed(__METHOD__, "missing vmDetails['{$param}']");
         }
     }
     // make sure the folder exists
     $config = $this->st->getConfig();
     if (!isset($config->storyplayer->modules->vagrant)) {
         throw new E5xx_ActionFailed(__METHOD__, "'vagrant' section missing in your storyplayer.json config file");
     }
     if (!isset($config->storyplayer->modules->vagrant->dir)) {
         throw new E5xx_ActionFailed(__METHOD__, "'dir' setting missing from 'vagrant' section of your storyplayer.json config file");
     }
     $pathToHomeFolder = $config->storyplayer->modules->vagrant->dir . '/' . $vmDetails->homeFolder;
     if (!is_dir($pathToHomeFolder)) {
         throw new E5xx_ActionFailed(__METHOD__, "VM dir '{$pathToHomeFolder}' does not exist");
     }
     // remember where the Vagrantfile is
     $vmDetails->dir = $pathToHomeFolder;
     // make sure the VM is stopped, if it is running
     $log->addStep("stop vagrant VM in '{$pathToHomeFolder}' if already running", function () use($vmDetails) {
         $command = "vagrant destroy --force";
         $this->runCommandAgainstHostManager($vmDetails, $command);
     });
     // remove any existing hosts table entry
     usingHostsTable()->removeHost($vmDetails->hostId);
     // remove any roles
     usingRolesTable()->removeHostFromAllRoles($vmDetails->hostId);
     // let's start the VM
     $command = "vagrant up";
     $result = $log->addStep("create vagrant VM in '{$pathToHomeFolder}'", function () use($command, $vmDetails) {
         return $this->runCommandAgainstHostManager($vmDetails, $command);
     });
     // did it work?
     if ($result->returnCode !== 0) {
         $log->endAction("VM failed to start or provision :(");
         throw new E5xx_ActionFailed(__METHOD__);
     }
     // yes it did!!
     // now, we need to know how to contact this VM
     $vmDetails->ipAddress = $this->determineIpAddress($vmDetails);
     $vmDetails->hostname = $this->determineHostname($vmDetails);
     // mark the box as provisioned
     // we will use this in stopBox() to avoid destroying VMs that failed
     // to provision
     $vmDetails->provisioned = true;
     // remember this vm, now that it is running
     usingHostsTable()->addHost($vmDetails->hostId, $vmDetails);
     // now, let's get this VM into our SSH known_hosts file, to avoid
     // prompting people when we try and provision this VM
     $log->addStep("get the VM into the SSH known_hosts file", function () use($vmDetails) {
         usingHost($vmDetails->hostId)->runCommand("ls");
     });
     // all done
     $log->endAction("VM successfully started; IP address is {$vmDetails->ipAddress}");
 }