Tobiasz Cudnik

Posts Tagged ‘phpQuery’

Test cases for websites using phpQuery and SimpleTest

In Ideas, phpQuery on 13.01.2009 at 23:09

Using phpQuery and some UnitTest framework (SimpleTest in this example) you can automatically test web page for presentence of specific part and it’s position. Set of such tests can save a lot of time during website development and after that even more.

Not only you can do simple tests like “are articles visible” but using WebBrowser plugin you can test whole process, let’s say a user registration. Example of such test i would like to present. This test will include following steps:

  1. Enter main page and follow the registration link
  2. Fill registration form and submit it
  3. Check if result is expected

Like i sad before, SimpleTest is framework of choice, but it doesn’t matter so much. What’s important:

  • WebBrowser needs callbacks
  • Callbacks should be declared as functions (for PHP < 5.3)
  • Inside callbacks, $this variable is unavailable
  • First step of the test should check if last step has succeeded

Full code below:

require('simpletest/autorun.php');
require('phpQuery/phpQuery.php');

class CustomerTest extends UnitTestCase {
	public static $_this;
	public static $registration = array(
		'username' => null,
		'success' => false
	);
	function testRegistration() {
		self::$_this = $this;
		phpQuery::browserGet('http://localhost/tested-site/',
			array('CustomerTest', '_testRegistrationLink')
		);
		$this->assertTrue(
			self::$registration['success'], "Registration unsuccessful"
		);
	}
	function _testRegistrationLink($browser) {
		$registrationLink = null;
		$browser->find('a:contains(rejestracja)')
			->WebBrowser(array('CustomerTest', '_testRegistrationForm'))
			->toReference($registrationLink)
				// jump to _testRegistrationForm
				->click();
		self::$_this->assertTrue(
			$registrationLink->length, "Registration link missing"
		);
	}
	function _testRegistrationForm($browser) {
		$registrationForm = null;
		$username = md5(microtime());
		$browser['.customers.form form']
			->toReference($registrationForm)
			->WebBrowser(array('CustomerTest', '_testRegistrationResult'))
			->find('input[name*=login]')->val($username)->end()
			->find('input[name*=email]')->val($username.'@test.com')->end()
			// jump to _testRegistrationResult
			->submit();
		self::$_this->assertTrue(
			$registrationForm->length, "Registration form missing"
		);
		self::$registration['username'] = $username;
	}
	function _testRegistrationResult($browser) {
		$loginForm = $browser->find('h2:text(Logowanie)');
		self::$_this->assertTrue($loginForm->length, "Login form missing");
		if ($loginForm->length)
			self::$registration['success'] = true;
	}
}

WebBrowser doesn’t support AJAX, so not all sites can be tested like this (although you can do it with AHAH after some work), but cookies and HTTP authentication should satisfy most needs.

Of course that’s noting new, projects accomplishing similar goal exist quite time now, eg jWebUnit, but neither of them have jQuery under the hood  ;)

Having fun using PHP 5.3 closures with phpQuery

In phpQuery on 08.01.2009 at 16:34

Some time ago i’ve wrote about new PHP 5.3 closures feature. Today i would like to show you it in action with phpQuery. If you’re using jQuery you will feel like in home :)

First example illustrates classic inline function, which is used to iterate over li nodes, incrementing each one’s content.

$markup = '
<ul>
	<li>1</li>
	<li>2</li>
	<li>3</li>
</ul>
';
$doc = phpQuery::newDocument($markup);
$doc['li']->each(function($node){
	pq($node)->text(
		pq($node)->text()+1
	);
});
print $doc;

Result will be someting like this (something because i’ve corrected indentation manually).

<ul>
	<li>2</li>
	<li>3</li>
	<li>4</li>
</ul>

Now more complicated stuff – scope inheritance. Scope inheritance means nothing else than ability to use variables declared outside code block (inline function in this case) inside this particular block. In JavaScript we have full inheritance right away. In PHP 5.3 we have to explicitly declare which variable we would like to inherit. It’s done by use keyword.

$markup = '
<div>
	<span>1</span>
	<span>2</span>
	<span>3</span></div>
<div>
	<span>1</span>
	<span>2</span>
	<span>3</span></div>
';
$doc = phpQuery::newDocument($markup);
$doc['div']->each(function($div){
	$div = pq($div);
	$div['span']->each(function($span) use ($div){
		pq($span)->insertBefore($div);
	});
});
print $doc;

We’ve nested one closure inside another. The inner one inherits $div variable from the outer. Lack of such feature for create_function was really problematic. Below you can see the result. Both divs are now empty.

<span>1</span>
<span>2</span>
<span>3</span>
<div></div>
<span>1</span>
<span>2</span>
<span>3</span>
<div></div>

Third example i want to show is about assigning inline function to a variable. It’s handful technique to avoid namespace collisions and of course allow easily pass closure thou the parts of code.

$markup = '
<div>1</div>
<div>2</div>
<div>3</div>
';
$callback = function($node){
	$node = pq($node);
	$node->text(
		'Callbacked: '.$node->text()
	);
};
print phpQuery::newDocument($markup)
	->find('div')->each($callback)->end();

Just how you suspect, every node’s content will be prefixed with “Callbacked: “.

<div>Callbacked: 1</div>
<div>Callbacked: 2</div>
<div>Callbacked: 3</div>

I think that this post clearly illustrates how closures work and that they are important for PHP as web development language.

If you would like to test code from this post and PHP 5.3 in general, download windows build from php.net or compile source yourself for other platform. CLI version will be enough, that means no apache module struggling.

QueryTemplates finally released

In QueryTemplates, plainTemplates on 03.12.2008 at 16:47

I’ve finally managed to release QueryTemplates, a pure HTML templating engine i’ve been working past months. There’re extensive examples which should allow anyone to easily understand the idea. Previously posted Pure HTML templates theory sums up some thoughts about this templating pattern. You can read more about new library on the wiki and there’s also an official blog. Feel free to post feedback.

Keep things from web up-to-date easily

In Web Scraping on 05.11.2008 at 17:11

When some project doesn’t use SVN or any other version-control system (or you can’t use it) you have to download things manually. I don’t have to say that nobody wants to do this, so what can you do to not do it ? You can simulate yourself doing it…

Example below downloads latest release of madwifi branch with new HAL (which i need for my WiFi adapter).

#!/usr/bin/php
find('table tr')->slice(-2, -1)->downloadTo('/target/local/path');
}
?>

Now, to get latest release all i need is to run above script from command line. One missing thing is checking if anything has changed but i leave it to you to resolve ;)

For files which names doesn’t change you can just use wget, like so:

wget 'http://host.net/somefile.zip' -O new-name.zip

jQuery Server Side ports

In The Net on 01.11.2008 at 11:03

jQuery besides achieving such successes as being used by Google or Micro$soft also has ports to other major languages. Most of them are designed to be server-side what opens doors for new uses to the library.

jQuery ports to other languages:

Below some snippets showing each implementation in few lines.

PHP

foreach($doc['ul > li'] as $li) {
  pq($li)->addClass('my-new-class');
      ->filter(':last')
        ->addClass('last-li');
}
$doc['ul > li:last']
  ->addClass('last-li');

Ruby

# load the RedHanded home page
doc = Hpricot(open("http://redhanded.hobix.com/index.html"))
# change the CSS class on links
(doc/"span.entryPermalink").set("class", "newLinks")

Perl

pQuery("http://google.com/search?q=pquery")
  ->find("h2")
    ->each(sub {
      my $i = shift;
        print $i + 1, ") ", pQuery($_)->text, "n";
      });

ActionScript

// add enterFrame event handler
$(stage).enterFrame(function(event:Event):void {
  $("RoundRect").attr("color", function(...args):uint {
    return Math.random() * 0xffffff;
  });
});

List comes from phpQuery wiki page. Do you know any other ports ? Share it in comments.

Update 09.12.08
New jQuery port has been released. This time it’s powered by Python and named PyQuery. Here’s the code:

Python

p = d("#hello")
p.addClass("toto")
p.attr.id = "plop"
p.prependTo(d('#test'))

phpQuery edits it’s own wiki

In phpQuery on 05.10.2008 at 3:05

phpQuery can connect to Google Code’s wiki editing form, authorize itself, replace page contents and finally submit the form.

// declare main variable
$pq = null;
// create dummy document as start point
phpQuery::newDocument('
<div>')
// authorize your google account
	->script('google_login')
// redirect authorized XHR component to googlecode's wiki form
	->location('http://code.google.com/p/phpquery/w/edit/test')
// save result as $pq, althought we could continue the chain
// but it would break in case of error...
		->toReference($pq);
if ($pq) {
// read about CallbackReference later in this post...
	$pq->WebBrowser(new CallbackReference($pq))
		->find('textarea:first')
			->val('lorem ipsum')
			->parents('form')
// first submit is Preview, so fire up second
				->find(':submit:eq(1)')
// triggering submit event thought input[type=submit]:click
// is a way to choose which submit is send
					->click();
	if ($pq) {
// print without  tags
		print $pq->script('safe_print');
	}
}

You can notice hot new feature – new CallbackReference($pq). Such callback sets first callback parameter to passed variable, by reference. Such pattern works with all methods accepting callbacks. Thanks to that, we can use if statements instead of function callbacks. In above example, CallbackReference object is called when click event is triggered.

Presented code snippet makes use of new Script plugin, particularly google_login.

Now it can be combined with automated XML documentation to wiki script, but this maybe for jQuery 1.3

Scripts plugin for phpQuery

In phpQuery on 05.10.2008 at 3:05

Scripts plugin is an easy file includer. Each script file have 4 variables in scope:

  • $self Represents $this
  • $params Represents parameters passed to script() method (without script name)
  • $return If not null, will be used as method result
  • $config Content of __config.php file

Possible use cases

  • Authorizations
  • Custom Chains
  • Simple phpQuery plugins
  • Sort functions
  • Website APIs
  • Event binding groups

Example

$return = $self->find($params[0]);

Actually there is only one script which is Google Login. It doesn’t support GMail yet, unfortunately.

Web Scraping with cli version of phpquery piped with sed

In Web Scraping, phpQuery on 05.10.2008 at 3:05

I’ve done what i was thinking about for some time. Terminal-firendly phpQuery CLI interface. Took about 10 minutes of coding… Works like this:

phpquery http://code.google.com/p/phpquery/downloads/list --find '.vt.col_4 a:first' --contents

This will return number of downloads latest phpQuery release file. Notice there is no need to quote url in any way. I was very happy with this so i’ve added callback support in text() and htmlOuter() methods, like so:

phpquery http://code.google.com/p/phpquery/downloads/list --find '.vt.col_4 a:first' --text strip_tags trim

When i had all stuff working, i’ve used it straight away to scrap forums and categories lists from old IPB v1.x. I’ve piped phpQuery result with sed, filtering final output.

// Fetch categories
./phpquery http://forum.wiadomosc.info/ --find '.maintitle a' | sed -r 's/^.*?c=([0-9]+).+?>(.+?)]*>([^<]*)<.*$/1: // 2/g'