I am starting a long running task that returns incremental output about the tasks progress with the Symfony Process component.
One of the examples shows how to get real time output and another example shows how to run an asynchronous task.
What I am trying to achieve is sto pass the result of getIncrementalOutput back to the ajax polling function so I can update the front end in real time.
It seems in either case the process->start() is blocking because my ajax call takes a minute to return and by that time the task has finished.
I guess I'm trying to avoid writing the progress to a db or a file and get the output directly from the running PHP task.
Not sure it's possible.
Although I don't fully understand what you want to create, I have written something similar and looking at it might answer your question:
First I created a Command that does the long-running task:
class GenerateCardBarcodesCommand extends Command
{
protected function configure()
{
$this
->setName('app:generate-card-barcodes')
->setDescription('Generate the customer cards with barcodes')
->addArgument('id', InputArgument::REQUIRED, 'id of the Loy/Card entity')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$id = $input->getArgument('id');
// generate stuff and put them in the database
}
}
In the controller the Process is started and there's an ajax action
class CardController extends Controller
{
public function newAction(Request $request)
{
// run command in background to start generating barcodes
// NOTE: unset DYLD_LIBRARY_PATH is a fix for MacOSX develop using MAMP.
// #see http://stackoverflow.com/questions/19008960/phantomjs-on-mac-os-x-works-from-the-command-line-not-via-exec
$process = new Process(sprintf('unset DYLD_LIBRARY_PATH ; php ../../apps/spin/console spin:loy:generate-card-barcodes %d', $entity->getId()));
$process->start();
sleep(1); // wait for process to start
// check for errors and output them through flashbag
if (!$process->isRunning())
if (!$process->isSuccessful())
$this->get('session')->getFlashBag()->add('error', "Oops! The process fininished with an error:".$process->getErrorOutput());
// otherwise assume the process is still running. It's progress will be displayed on the card index
return $this->redirect($this->generateUrl('loy_card'));
}
public function ajaxCreateBarcodesAction($id)
{
$em = $this->getDoctrine()->getManager();
$entity = $this->getEntity($id);
$count = (int)$em->getRepository('ExtendasSpinBundle:Loy\CustomerCard')->getCount($entity);
return new Response(floor($count / ($entity->getNoCards() / 100)));
}
}
// in the twig template the ajax is retrieved, which is simply a number from 0 to 100, which is used in the jquery ui progressbar.
{{ 'Processing'|trans }}...
<script type="text/javascript">
$(function() {
function pollLatestResponse() {
$.get("{{ path('loy_card_ajax_generate_barcodes', {'id': entity[0].id}) }}").done(function (perc) {
if (perc == 100)
{
clearInterval(pollTimer);
$('#download-{{entity[0].id}}').show();
$('#progress-{{entity[0].id}}').hide();
}
else
{
$('#progress-{{entity[0].id}}').progressbar("value", parseInt(perc));
}
});
}
var pollTimer;
$(document).ready(function () {
$('#progress-{{entity[0].id}}').progressbar({"value": false});
pollTimer = setInterval(pollLatestResponse, 2000);
});
});
</script>
Related
When I click on a button, a new page opens with a form and I need to fill a field on that page.
However, as soon as the page starts loading, behat attempts to populate the field that has not yet been loaded.
I would like to put an implicit wait to wait for the field to be displayed before attempting to populate it.
/**
* #Given que preencho corretamente os campos da tela
*/
public function quePreenchoCorretamenteOsCamposDaTela()
{
$faker = Faker\Factory::create();
$this->getPage()->findField('voucher_subject')->setValue($faker->text);
$this->getPage()->findField('voucher_nameRecipient')->setValue($faker->name);
}
Does anyone can help me?
From my point of view this can be done more elegant now:
$page = $this->getSession()->getPage();
$page->waitFor(5,
function () use ($page) {
$field = $page->findField('voucher_subject');
return $field && $field->isVisible();
}
);
You can also wrap this inside some private function.
You can use spin function:
trait FeatureContextHelper
{
public function spin (callable $lambda, $wait = 5)
{
$lastErrorMessage = '';
for ($i = 0; $i < $wait; $i++) {
try {
if ($lambda($this)) {
return true;
}
} catch (Exception $e) {
// do nothing
$lastErrorMessage = $e->getMessage();
}
sleep(1);
}
throw new ElementNotVisible('The element is not visible ' . $lastErrorMessage);
}
}
Then in your context:
class FeatureContext extends MinkContext
{
use FeatureContextHelper;
/**
* #Given que preencho corretamente os campos da tela
*/
public function quePreenchoCorretamenteOsCamposDaTela()
{
$this->spin(function ($context) {
$faker = Faker\Factory::create();
$context->getSession()->getPage()->findField('voucher_subject')->setValue($faker->text);
$context->getSession()->getPage()->findField('voucher_nameRecipient')->setValue($faker->name);
return true;
}
}
}
It will try to find the element within 5 seconds and then timeout if it didn't find it. It works for us very well with Selenium2 and Goutte.
If you're using a driver that does only simulate the browser (like BrowserKit or Goutte), behat will have control back only when DOM is correctly composed and ready (and of course no js can be interpreted or executed). If you use something like Selenium2 and the field is built from an asynchronous call (If I understand correctly, this is your case), is up to you to be sure that page is loaded in its entirety. That's because the request has a response and the control is passed back to Behat process.
One possibile solution at this problem is to append a class to the body right before each ajax/async call and strip it out right after every call finished. Then, create a "spinner" function in your behat context to check for the class to be gone.
I am not very familiar with javascript and not sure how to handle alerts in php script when using phantomjs.
This is my code:
$this->clickcontrol(Constants::LINK, 'delete', false);
$this->acceptAlert();
So how should I change this to handle alerts in phantomjs
This looks like PHPUnit's PHPUnit_Extensions_Selenium2TestCase.
When faced with this, I have created following function (I put it into a common base test case class myself, but it also can be in your test class):
protected function waitForAlert($expectedText, $timeout = 10000)
{
$this->waitUntil(
function () use ($expectedText) {
if ($this->alertText() == $expectedText) {
return true;
}
},
$timeout
);
$this->acceptAlert();
}
Then in the test itself you can use it as such:
$this->waitForAlert('You need a complete profile');
If there is no alert it will fail after the timeout set
Hope this helps ;)
I'm trying to run a job queue to create a PDF file using SlmQueueBeanstalkd and DOMPDFModule in ZF".
Here's what I'm doing in my controller:
public function reporteAction()
{
$job = new TareaReporte();
$queueManager = $this->serviceLocator->get('SlmQueue\Queue\QueuePluginManager');
$queue = $queueManager->get('myQueue');
$queue->push($job);
...
}
This is the job:
namespace Application\Job;
use SlmQueue\Job\AbstractJob;
use SlmQueue\Queue\QueueAwareInterface;
use SlmQueue\Queue\QueueInterface;
use DOMPDFModule\View\Model\PdfModel;
class TareaReporte extends AbstractJob implements QueueAwareInterface
{
protected $queue;
public function getQueue()
{
return $this->queue;
}
public function setQueue(QueueInterface $queue)
{
$this->queue = $queue;
}
public function execute()
{
$sm = $this->getQueue()->getJobPluginManager()->getServiceLocator();
$empresaTable = $sm->get('Application\Model\EmpresaTable');
$registros = $empresaTable->listadoCompleto();
$model = new PdfModel(array('registros' => $registros));
$model->setOption('paperSize', 'letter');
$model->setOption('paperOrientation', 'portrait');
$model->setTemplate('empresa/reporte-pdf');
$output = $sm->get('viewPdfrenderer')->render($model);
$filename = "/path/to/pdf/file.pdf";
file_put_contents($filename, $output);
}
}
The first time you run it, the file is created and the work is successful, however, if you run a second time, the task is buried and the file is not created.
It seems that stays in an endless cycle when trying to render the model a second time.
I've had a similar issue and it turned out it was because of the way ZendPdf\PdfDocument reuses it's object factory. Are you using ZendPdf\PdfDocument?
You might need to correctly close factory.
class MyDocument extends PdfDocument
{
public function __destruct()
{
$this->_objFactory->close();
}
}
Try to add this or something similar to the PdfDocument class...
update : it seem you are not using PdfDocument, however I suspect this is the issue is the same. Are you able to regenerate a second PDF in a normal http request? It is your job to make sure the environment is equal on each run.
If you are unable to overcome this problem a short-term quick solution would be to set max_runs configuration for SlmQueue to 1. That way the worker is stopped after each job and this reset to a vanilla state...
I need to return from a function call once a React/Promise has been resolved. The basic idea is to fake a synchronous call from an ansynchronous one. This means that the outer function must return a value once a promise has been resolved or rejected.
This is to create a driver for RedBeanPHP using React/Mysql. I am aware that this will likely lead to CPU starvation in the React event loop.
My initial idea was to use a generator then call yield inside a \React\Promise\Deferred::then callback.
function synchronous()
{
$result = asynchronous();
}
function asynchronous()
{
$deferred = new \React\Promise\Deferred;
$sleep = function() use ($deferred)
{
sleep(5);
$deferred->resolve(true);
};
$deferred->then(function($ret) {
yield $ret;
});
$sleep();
}
The PHP generator class, AFAICT, is only directly constructable by the PHP engine itself. The then callback would need to directly invoke send on the generator of the asynchronous function for this to work.
PHP lacks both continuations as well as generator delegation, which would make it possible to call yield from inside a nested callback, making this entirely impossible to achieve for the moment.
ReactPhp offers the async tools package which has an await function.
Code can then become:
function synchronous()
{
$result = \React\Async\await(asynchronous());
}
function asynchronous()
{
$deferred = new \React\Promise\Deferred;
$sleep = function() use ($deferred)
{
sleep(5);
$deferred->resolve(true);
};
$sleep();
return $deferred->promise();
}
Scenario: Modify and save an incomplete change to a Campaign
Given I click on the Campaign section folder
And I press Save in the selected Campaign
Then I should see an error balloon informing the changes cannot be saved
Point is that this 'error balloon' in the final step is a ajax call which will then bring a green or red balloon according to the success of the operation. Currently what I do is after
'And I press Save...' I will do a sleep(3) to give it time for this balloon to show up. This doesn't seem very smart coz you are wasting time and also because some times it can take more or less time for this call to be processed.
How do you guys make your behat tests wait for Ajax do be done instead of just putting the beasts to sleep?
thank you very much for any feedback!
This is done by waiting for your outstanding ajax calls to hit 0. jQuery.active will check just that for you.
In your FeatureContext.php, you can do something like;
public function iShouldSeeAnErrorBalloon($title)
{
$time = 5000; // time should be in milliseconds
$this->getSession()->wait($time, '(0 === jQuery.active)');
// asserts below
}
And do make sure you use a Mink Driver that runs javascript and ajax (the default does not).
I do it by waiting for the DOM to change as a result of the Ajax Call. I made a subclass of DocumentElement, calling it AsyncDocumentElement and overriding the findAll method:
public function findAll($selector, $locator, $waitms=5000)
{
$xpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($selector, $locator);
// add parent xpath before element selector
if (0 === strpos($xpath, '/')) {
$xpath = $this->getXpath().$xpath;
} else {
$xpath = $this->getXpath().'/'.$xpath;
}
$page = $this->getSession()->getPage();
// my code to wait until the xpath expression provides an element
if ($waitms && !($this->getSession()->getDriver() instanceof \Behat\Symfony2Extension\Driver\KernelDriver)) {
$templ = 'document.evaluate("%s", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null ).snapshotLength > 0;';
$waitJs = sprintf($templ, $xpath);
$this->getSession()->wait($waitms, $waitJs);
}
return $this->getSession()->getDriver()->find($xpath);
}
Then in \Behat\Mink\Session I changed the constructor to use that class.
public function __construct(DriverInterface $driver, SelectorsHandler $selectorsHandler = null)
{
$driver->setSession($this);
if (null === $selectorsHandler) {
$selectorsHandler = new SelectorsHandler();
}
$this->driver = $driver;
$this->page = new AsyncDocumentElement($this);
$this->selectorsHandler = $selectorsHandler;
}
Once I did this, I found my AngularJS tests were working. So far, I've only tested in Firefox.
In case you are using Prototypejs (e.g Magento), the equivalent code is:
public function iShouldSeeAnErrorBalloon($title)
{
$this->getSession()->wait($duration, '(0 === Ajax.activeRequestCount)');
// asserts below
}