File Coverage

blib/lib/Test2/MojoX.pm
Criterion Covered Total %
statement 331 389 85.0
branch 29 54 53.7
condition 18 40 45.0
subroutine 68 78 87.1
pod 51 51 100.0
total 497 612 81.2


line stmt bran cond sub pod time code
1             package Test2::MojoX;
2 10     10   4041994 use Mojo::Base -base;
  10         70  
  10         108  
3              
4             our $VERSION = 0.06;
5              
6             ## "Amy: He knows when you are sleeping.
7             ## Professor: He knows when you're on the can.
8             ## Leela: He'll hunt you down and blast your ass from here to Pakistan.
9             ## Zoidberg: Oh.
10             ## Hermes: You'd better not breathe, you'd better not move.
11             ## Bender: You're better off dead, I'm telling you, dude.
12             ## Fry: Santa Claus is gunning you down!"
13 10     10   7686 use Mojo::IOLoop;
  10         1703367  
  10         65  
14 10     10   573 use Mojo::JSON 'j';
  10         24  
  10         610  
15 10     10   4954 use Mojo::JSON::Pointer;
  10         7085  
  10         93  
16 10     10   5280 use Mojo::Server;
  10         15874  
  10         88  
17 10     10   6671 use Mojo::UserAgent;
  10         973455  
  10         118  
18 10     10   582 use Mojo::Util qw(decode encode monkey_patch);
  10         22  
  10         675  
19 10     10   69 use Test2::API qw(context context_do);
  10         147  
  10         541  
20 10     10   71 use Test2::V0;
  10         21  
  10         118  
21              
22             has [qw(message success tx)];
23             has ua =>
24             sub { Mojo::UserAgent->new(insecure => 1)->ioloop(Mojo::IOLoop->singleton) };
25              
26             # Silent or loud tests
27             $ENV{MOJO_LOG_LEVEL} ||= $ENV{HARNESS_IS_VERBOSE} ? 'debug' : 'fatal';
28              
29             for my $method (qw(delete get head options patch post put)) {
30             monkey_patch __PACKAGE__, "${method}_ok", sub {
31 68     68   288468 my ($self, $url) = (shift, shift);
        68      
        68      
        68      
        68      
        68      
        68      
32 68         267 my @args = @_;
33             context_do {
34 68     68   6779 my $ctx = context;
35 68         4030 $self->_request_ok($self->ua->build_tx(uc $method, $url, @args), $url);
36 68         847 $ctx->release;
37 68         504 };
38 68         4394 return $self;
39             };
40             }
41              
42             sub app {
43 4     4 1 1074 my ($self, $app) = @_;
44 4 100       19 return $self->ua->server->app unless $app;
45 1         4 $self->ua->server->app($app);
46 1         20 return $self;
47             }
48              
49             sub attr_is {
50 0     0 1 0 my ($self, $selector, $attr, $value, $desc) = @_;
51 0         0 my $ctx = context;
52 0         0 $desc = _desc($desc,
53             qq{exact match for attribute "$attr" at selector "$selector"});
54 0         0 my $out = is($self->_attr($selector, $attr), $value, $desc);
55 0         0 $ctx->release;
56 0         0 return $self->success($out);
57             }
58              
59             sub attr_isnt {
60 0     0 1 0 my ($self, $selector, $attr, $value, $desc) = @_;
61 0         0 my $ctx = context;
62 0         0 $desc
63             = _desc($desc, qq{no match for attribute "$attr" at selector "$selector"});
64 0         0 my $out = isnt($self->_attr($selector, $attr), $value, $desc);
65 0         0 $ctx->release;
66 0         0 return $self->success($out);
67             }
68              
69             sub attr_like {
70 0     0 1 0 my ($self, $selector, $attr, $regex, $desc) = @_;
71 0         0 my $ctx = context;
72 0         0 $desc = _desc($desc,
73             qq{similar match for attribute "$attr" at selector "$selector"});
74 0         0 my $out = like($self->_attr($selector, $attr), $regex, $desc);
75 0         0 $ctx->release;
76 0         0 return $self->success($out);
77             }
78              
79             sub attr_unlike {
80 0     0 1 0 my ($self, $selector, $attr, $regex, $desc) = @_;
81 0         0 my $ctx = context;
82 0         0 $desc = _desc($desc,
83             qq{no similar match for attribute "$attr" at selector "$selector"});
84 0         0 my $out = unlike($self->_attr($selector, $attr), $regex, $desc);
85 0         0 $ctx->release;
86 0         0 return $self->success($out);
87             }
88              
89             sub content_is {
90 6     6 1 345 my ($self, $check, $desc) = @_;
91 6         18 my $ctx = context;
92 6         663 my $out
93             = is($self->tx->res->text, $check, _desc($desc, 'exact match for content'));
94 6         15209 $ctx->release;
95 6         153 return $self->success($out);
96             }
97              
98             sub content_isnt {
99 2     2 1 7 my ($self, $check, $desc) = @_;
100 2         7 my $ctx = context;
101 2         178 my $out
102             = isnt($self->tx->res->text, $check, _desc($desc, 'no match for content'));
103 2         1235 $ctx->release;
104 2         50 return $self->success($out);
105             }
106              
107             sub content_like {
108 2     2 1 9 my ($self, $check, $desc) = @_;
109 2         17 my $ctx = context;
110 2         176 my $out
111             = like($self->tx->res->text, $check, _desc($desc, 'content is similar'));
112 2         4693 $ctx->release;
113 2         50 return $self->success($out);
114             }
115              
116             sub content_type_is {
117 2     2 1 7 my ($self, $check, $desc) = @_;
118 2         8 my $ctx = context;
119 2   33     220 $desc = _desc($desc, ref $check || "Content-Type: $check");
120 2         25 my $out = is($self->tx->res->headers->content_type, $check, $desc);
121 2         5490 $ctx->release;
122 2         51 return $self->success($out);
123             }
124              
125             sub content_type_isnt {
126 2     2 1 9 my ($self, $check, $desc) = @_;
127 2 50       13 $desc = _desc($desc,
128             ref $check ? 'not ' . ref $check : "not Content-Type: $check");
129 2         23 my $ctx = context;
130 2         210 my $out = isnt($self->tx->res->headers->content_type, $check, $desc);
131 2         1398 $ctx->release;
132 2         68 return $self->success($out);
133             }
134              
135             sub content_type_like {
136 2     2 1 7 my ($self, $check, $desc) = @_;
137 2         7 my $ctx = context;
138 2         207 $desc = _desc($desc, 'Content-Type is similar');
139 2         24 my $out = like($self->tx->res->headers->content_type, $check, $desc);
140 2         5671 $ctx->release;
141 2         55 return $self->success($out);
142             }
143              
144             sub content_type_unlike {
145 2     2 1 8 my ($self, $regex, $desc) = @_;
146 2         7 my $ctx = context;
147 2         205 $desc = _desc($desc, 'Content-Type is not similar');
148 2         25 my $out = unlike($self->tx->res->headers->content_type, $regex, $desc);
149 2         1418 $ctx->release;
150 2         52 return $self->success($out);
151             }
152              
153             sub content_unlike {
154 2     2 1 8 my ($self, $regex, $desc) = @_;
155 2         7 my $ctx = context;
156 2         179 my $out = unlike($self->tx->res->text, $regex,
157             _desc($desc, 'content is not similar'));
158 2         1197 $ctx->release;
159 2         58 return $self->success($out);
160             }
161              
162             sub element_count_is {
163 2     2 1 7 my ($self, $selector, $check, $desc) = @_;
164 2         7 my $ctx = context;
165 2         196 my $size = $self->tx->res->dom->find($selector)->size;
166 2         2694 my $out = is($size, $check,
167             _desc($desc, qq{element count for selector "$selector"}));
168 2         4893 $ctx->release;
169 2         54 return $self->success($out);
170             }
171              
172             sub element_exists {
173 2     2 1 11 my ($self, $selector, $desc) = @_;
174 2         7 my $ctx = context;
175 2         189 $desc = _desc($desc, qq{element for selector "$selector" exists});
176 2         23 my $out = ok($self->tx->res->dom->at($selector), $desc);
177 2         2603 $ctx->release;
178 2         51 return $self->success($out);
179             }
180              
181             sub element_exists_not {
182 2     2 1 8 my ($self, $selector, $desc) = @_;
183 2         7 my $ctx = context;
184 2         186 $desc = _desc($desc, qq{no element for selector "$selector"});
185 2         23 my $out = ok(!$self->tx->res->dom->at($selector), $desc);
186 2         2471 $ctx->release;
187 2         51 return $self->success($out);
188             }
189              
190             sub finish_ok {
191 3     3 1 21 my $self = shift;
192 3         10 my $ctx = context;
193 3         371 my $out;
194 3 100       13 if ($self->tx->is_websocket) {
195 2         29 $self->tx->finish(@_);
196 2         361 Mojo::IOLoop->one_tick while !$self->{finished};
197 2         247 $out = ok(1, 'closed WebSocket');
198             }
199             else {
200 1         14 $out = ok(0, 'connection is not WebSocket');
201             }
202 3         900 $ctx->release;
203 3         81 return $self->success($out);
204             }
205              
206             sub finished_ok {
207 2     2 1 30 my ($self, $code) = @_;
208 2         9 my $ctx = context;
209 2         192 Mojo::IOLoop->one_tick while !$self->{finished};
210             $ctx->diag("WebSocket closed with out $self->{finished}[0]")
211 2 100       17 unless my $ok = $self->{finished}[0] == $code;
212 2         249 my $out = ok($ok, "WebSocket closed with out $code");
213 2         615 $ctx->release;
214 2         56 return $self->success($out);
215             }
216              
217             sub header_exists {
218 2     2 1 8 my ($self, $name, $desc) = @_;
219 2         7 my $ctx = context;
220 2         237 $desc = _desc($desc, qq{header "$name" exists});
221 2         23 my $out = ok(!!@{$self->tx->res->headers->every_header($name)}, $desc);
  2         10  
222 2         747 $ctx->release;
223 2         52 return $self->success($out);
224             }
225              
226             sub header_exists_not {
227 2     2 1 10 my ($self, $name, $desc) = @_;
228 2         8 my $ctx = context;
229 2         199 $desc = _desc($desc, qq{no "$name" header});
230 2         24 my $out = ok(!@{$self->tx->res->headers->every_header($name)}, $desc);
  2         7  
231 2         901 $ctx->release;
232 2         53 return $self->success($out);
233             }
234              
235             sub header_is {
236 2     2 1 9 my ($self, $name, $check, $desc) = @_;
237 2         7 my $ctx = context;
238 2   33     201 $desc = _desc($desc, "$name: " . (ref $check || $check // ''));
      50        
239 2         25 my $out = is($self->tx->res->headers->header($name), $check, $desc);
240 2         5724 $ctx->release;
241 2         54 return $self->success($out);
242             }
243              
244             sub header_isnt {
245 2     2 1 8 my ($self, $name, $check, $desc) = @_;
246 2         8 my $ctx = context;
247 2   33     193 $desc = _desc($desc, "not $name: " . (ref $check || $check // ''));
      50        
248 2         27 my $out = isnt($self->tx->res->headers->header($name), $check, $desc);
249 2         1303 $ctx->release;
250 2         49 return $self->success($out);
251             }
252              
253             sub header_like {
254 2     2 1 8 my ($self, $name, $check, $desc) = @_;
255 2         8 my $ctx = context;
256 2         205 $desc = _desc($desc, "$name is similar");
257 2         26 my $out = like($self->tx->res->headers->header($name), $check, $desc);
258 2         5457 $ctx->release;
259 2         52 return $self->success($out);
260             }
261              
262             sub header_unlike {
263 2     2 1 11 my ($self, $name, $regex, $desc) = @_;
264 2         6 my $ctx = context;
265 2         206 $desc = _desc($desc, "$name is not similar");
266 2         27 my $out = unlike($self->tx->res->headers->header($name), $regex, $desc);
267 2         1364 $ctx->release;
268 2         52 return $self->success($out);
269             }
270              
271             sub json_has {
272 2     2 1 5 my ($self, $p, $desc) = @_;
273 2         8 my $ctx = context;
274 2         189 $desc = _desc($desc, qq{has value for JSON Pointer "$p"});
275 2         25 my $out
276             = ok(!!Mojo::JSON::Pointer->new($self->tx->res->json)->contains($p), $desc);
277 2         1532 $ctx->release;
278 2         51 return $self->success($out);
279             }
280              
281             sub json_hasnt {
282 2     2 1 6 my ($self, $p, $desc) = @_;
283 2         6 my $ctx = context;
284 2         185 $desc = _desc($desc, qq{has no value for JSON Pointer "$p"});
285 2         24 my $out
286             = ok(!Mojo::JSON::Pointer->new($self->tx->res->json)->contains($p), $desc);
287 2         1343 $ctx->release;
288 2         51 return $self->success($out);
289             }
290              
291             sub json_is {
292 3     3 1 797 my $self = shift;
293 3         9 my $ctx = context;
294 3 100       282 my ($p, $data) = @_ > 1 ? (shift, shift) : ('', shift);
295 3         11 my $desc = _desc(shift, qq{exact match for JSON Pointer "$p"});
296 3         35 my $out = is($self->tx->res->json($p), $data, $desc);
297 3         7077 $ctx->release;
298 3         82 return $self->success($out);
299             }
300              
301             sub json_like {
302 3     3 1 389 my $self = shift;
303 3 100       15 my ($p, $check) = @_ > 1 ? (shift, shift) : ('', shift);
304 3         11 my $ctx = context;
305 3         276 my $out = like($self->tx->res->json($p),
306             $check, _desc(shift, qq{similar match for JSON Pointer "$p"}));
307 3         5655 $ctx->release;
308 3         78 return $self->success($out);
309             }
310              
311             sub json_message_has {
312 2     2 1 33 my ($self, $p, $desc) = @_;
313 2         5 my $ctx = context;
314 2         179 $desc = _desc($desc, qq{has value for JSON Pointer "$p"});
315 2         22 my $out = ok($self->_json(contains => $p), $desc);
316 2         1085 $ctx->release;
317 2         52 return $self->success($out);
318             }
319              
320             sub json_message_hasnt {
321 2     2 1 30 my ($self, $p, $desc) = @_;
322 2         7 my $ctx = context;
323 2         177 $desc = _desc($desc, qq{has no value for JSON Pointer "$p"});
324 2         24 my $out = ok(!$self->_json(contains => $p), $desc);
325 2         1101 $ctx->release;
326 2         50 return $self->success($out);
327             }
328              
329             sub json_message_is {
330 2     2 1 486 my $self = shift;
331 2         7 my $ctx = context;
332 2 50       197 my ($p, $data) = @_ > 1 ? (shift, shift) : ('', shift);
333 2         20 my $desc = _desc(shift, qq{exact match for JSON Pointer "$p"});
334 2         27 my $out = is($self->_json(get => $p), $data, $desc);
335 2         10337 $ctx->release;
336 2         52 return $self->success($out);
337             }
338              
339             sub json_message_like {
340 2     2 1 349 my $self = shift;
341 2         8 my $ctx = context;
342 2 50       213 my ($p, $check) = @_ > 1 ? (shift, shift) : ('', shift);
343 2         8 my $out = like($self->_json(get => $p),
344             $check, _desc(shift, qq{similar match for JSON Pointer "$p"}));
345 2         8724 $ctx->release;
346 2         53 return $self->success($out);
347             }
348              
349             sub json_message_unlike {
350 2     2 1 406 my $self = shift;
351 2         8 my $ctx = context;
352 2 50       217 my ($p, $data) = @_ > 1 ? (shift, shift) : ('', shift);
353 2         8 my $out = unlike($self->_json(get => $p),
354             $data, _desc(shift, qq{no similar match for JSON Pointer "$p"}));
355 2         1683 $ctx->release;
356 2         52 return $self->success($out);
357             }
358              
359             sub json_unlike {
360 3     3 1 367 my $self = shift;
361 3         9 my $ctx = context;
362 3 100       276 my ($p, $data) = @_ > 1 ? (shift, shift) : ('', shift);
363 3         12 my $out = unlike($self->tx->res->json($p),
364             $data, _desc(shift, qq{no similar match for JSON Pointer "$p"}));
365 3         2312 $ctx->release;
366 3         77 return $self->success($out);
367             }
368              
369             sub message_is {
370 2     2 1 38 my ($self, $check, $desc) = @_;
371 2         6 my $ctx = context;
372 2         187 $self->_message('is', $check, _desc($desc, 'exact match for message'));
373 2         24 $ctx->release;
374 2         48 return $self;
375             }
376              
377             sub message_isnt {
378 0     0 1 0 my ($self, $check, $desc) = @_;
379 0         0 my $ctx = context;
380 0         0 $self->_message('isnt', $check, _desc($desc, 'no match for message'));
381 0         0 $ctx->release;
382 0         0 return $self;
383             }
384              
385             sub message_like {
386 0     0 1 0 my ($self, $check, $desc) = @_;
387 0         0 my $ctx = context;
388 0         0 $self->_message('like', $check, _desc($desc, 'message is similar'));
389 0         0 $ctx->release;
390 0         0 return $self;
391             }
392              
393             sub message_ok {
394 12     12 1 176 my ($self, $desc) = @_;
395 12         31 my $ctx = context;
396 12         1091 my $out = ok(!!$self->_wait, _desc($desc, 'message received'));
397 12         2624 $ctx->release;
398 12         300 return $self->success($out);
399             }
400              
401             sub message_unlike {
402 0     0 1 0 my ($self, $regex, $desc) = @_;
403 0         0 my $ctx = context;
404 0         0 $self->_message('unlike', $regex, _desc($desc, 'message is not similar'));
405 0         0 $ctx->release;
406 0         0 return $self;
407             }
408              
409             sub new {
410 10     10 1 11230 my $self = shift->SUPER::new;
411              
412 10 50       110 return $self unless my $app = shift;
413              
414 0 0       0 my @args = @_ ? {config => {config_override => 1, %{shift()}}} : ();
  0         0  
415 0 0       0 return $self->app(Mojo::Server->new->build_app($app, @args)) unless ref $app;
416 0 0       0 $app = Mojo::Server->new->load_app($app) unless $app->isa('Mojolicious');
417 0 0       0 return $self->app(@args ? $app->config($args[0]{config}) : $app);
418             }
419              
420             sub or {
421 0     0 1 0 my ($self, $cb) = @_;
422 0 0       0 $self->$cb unless $self->success;
423 0         0 return $self;
424             }
425              
426             sub request_ok {
427 1     1 1 30357 my $self = shift;
428 1         3 my $tx = $_[0];
429             context_do {
430 1     1   99 my $ctx = context;
431 1         67 $self->_request_ok($tx, $tx->req->url->to_string);
432 1         18 $ctx->release;
433 1         12 };
434 1         90 return $self;
435             }
436              
437             sub reset_session {
438 0     0 1 0 my $self = shift;
439 0         0 $self->ua->cookie_jar->empty;
440 0         0 return $self->tx(undef);
441             }
442              
443             sub send_ok {
444 14     14 1 173 my ($self, $msg, $desc) = @_;
445 14         41 my $ctx = context;
446              
447 14         1294 $desc = _desc($desc, 'send message');
448 14         132 my $out;
449 14 100       45 if ($self->tx->is_websocket) {
450 13     13   109 $self->tx->send($msg => sub { Mojo::IOLoop->stop });
  13         3697  
451 13         3368 Mojo::IOLoop->start;
452 13         1550 $out = ok(1, $desc);
453             }
454             else {
455 1         11 $out = ok(0, $desc);
456             }
457 14         2818 $ctx->release;
458 14         365 return $self->success($out);
459             }
460              
461             sub status_is {
462 7     7 1 346 my ($self, $check, $desc) = @_;
463 7         20 my $ctx = context;
464 7   66     691 $desc = _desc($desc,
465             ref $check || "$check " . $self->tx->res->default_message($check));
466 7         83 my $out = is($self->tx->res->code, $check, $desc);
467 7         10674 $ctx->release;
468 7         189 return $self->success($out);
469             }
470              
471             sub status_isnt {
472 2     2 1 5 my ($self, $check, $desc) = @_;
473 2         7 my $ctx = context;
474 2 50       186 $desc = _desc($desc,
475             ref $check
476             ? 'not ' . ref $check
477             : "not $check " . $self->tx->res->default_message($check));
478 2         22 my $out = isnt($self->tx->res->code, $check, $desc);
479 2         1184 $ctx->release;
480 2         61 return $self->success($out);
481             }
482              
483             sub text_is {
484 2     2 1 10 my ($self, $selector, $check, $desc) = @_;
485 2         8 my $ctx = context;
486 2         218 my $out = is($self->_text($selector),
487             $check, _desc($desc, qq{exact match for selector "$selector"}));
488 2         5119 $ctx->release;
489 2         74 return $self->success($out);
490             }
491              
492             sub text_isnt {
493 2     2 1 9 my ($self, $selector, $check, $desc) = @_;
494 2         9 my $ctx = context;
495 2         219 my $out = isnt($self->_text($selector),
496             $check, _desc($desc, qq{no match for selector "$selector"}));
497 2         1566 $ctx->release;
498 2         55 return $self->success($out);
499             }
500              
501             sub text_like {
502 2     2 1 8 my ($self, $selector, $check, $desc) = @_;
503 2         7 my $ctx = context;
504 2         204 my $out = like($self->_text($selector),
505             $check, _desc($desc, qq{similar match for selector "$selector"}));
506 2         4894 $ctx->release;
507 2         52 return $self->success($out);
508             }
509              
510             sub text_unlike {
511 2     2 1 8 my ($self, $selector, $regex, $desc) = @_;
512 2         7 my $ctx = context;
513 2         184 my $out = unlike($self->_text($selector),
514             $regex, _desc($desc, qq{no similar match for selector "$selector"}));
515 2         1230 $ctx->release;
516 2         51 return $self->success($out);
517             }
518              
519             sub websocket_ok {
520 15     15 1 79156 my $self = shift;
521 15         67 my $ctx = context;
522 15         1303 $self->_request_ok($self->ua->build_websocket_tx(@_), $_[0]);
523 15         196 $ctx->release;
524 15         391 return $self;
525             }
526              
527             sub _attr {
528 0     0   0 my ($self, $selector, $attr) = @_;
529 0 0       0 return '' unless my $e = $self->tx->res->dom->at($selector);
530 0   0     0 return $e->attr($attr) || '';
531             }
532              
533 190   66 190   7420 sub _desc { encode 'UTF-8', shift || shift }
534              
535             sub _json {
536 10     10   26 my ($self, $method, $p) = @_;
537 10   50     15 return Mojo::JSON::Pointer->new(j(@{$self->message // []}[1]))->$method($p);
  10         29  
538             }
539              
540             sub _message {
541 2     2   31 my ($self, $name, $value, $desc) = @_;
542 2         7 my $ctx = context;
543 2   50     148 my ($type, $msg) = @{$self->message // []};
  2         10  
544              
545             # Type check
546 2 50       19 if (ref $value eq 'HASH') {
547 0 0       0 my $expect = exists $value->{text} ? 'text' : 'binary';
548 0         0 $value = $value->{$expect};
549 0 0 0     0 $msg = '' if ($type // '') ne $expect;
550             }
551              
552             # Decode text frame if there is no type check
553 2 50 50     16 else { $msg = decode 'UTF-8', $msg if ($type // '') eq 'text' }
554              
555 2   50     71 my $out = !!Test2::V0->can($name)->($msg // '', $value, $desc);
556 2         5138 $ctx->release;
557 2         24 return $self->success($out);
558             }
559              
560             sub _request_ok {
561 84     84   22426 my ($self, $tx, $url) = @_;
562              
563 84         301 my $ctx = context;
564 84         5959 my $out;
565              
566             # Establish WebSocket connection
567 84 100       325 if ($tx->req->is_handshake) {
568 15         407 @$self{qw(finished messages)} = (undef, []);
569             $self->ua->start(
570             $tx => sub {
571 15     15   177365 my ($ua, $tx) = @_;
572 15 100       68 $self->{finished} = [] unless $self->tx($tx)->tx->is_websocket;
573 15         341 $tx->on(finish => sub { shift; $self->{finished} = [@_] });
  2         4335  
  2         8  
574 15         146 $tx->on(binary => sub { push @{$self->{messages}}, [binary => pop] });
  0         0  
  0         0  
575 15         147 $tx->on(text => sub { push @{$self->{messages}}, [text => pop] });
  13         10792  
  13         56  
576 15         159 Mojo::IOLoop->stop;
577             }
578 15         58 );
579 15         25503 Mojo::IOLoop->start;
580              
581 15         3035 my $desc = _desc("WebSocket handshake with $url");
582 15         188 $out = ok($self->tx->is_websocket, $desc);
583             }
584             else {
585             # Perform request
586 69         1820 $self->tx($self->ua->start($tx));
587 69         1308443 my $err = $self->tx->error;
588             $ctx->diag($err->{message})
589 69 50 66     1823 if !(my $ok = !$err->{message} || $err->{code}) && $err;
      33        
590 69         196 $out = ok($ok, _desc("@{[uc $tx->req->method]} $url"));
  69         198  
591             }
592              
593 84         21301 $ctx->release;
594 84         998 return $self->success($out);
595             }
596              
597             sub _text {
598 8 100   8   32 return '' unless my $e = shift->tx->res->dom->at(shift);
599 6         5260 return $e->text;
600             }
601              
602             sub _wait {
603 11     11   23 my $self = shift;
604 11   66     40 Mojo::IOLoop->one_tick while !$self->{finished} && !@{$self->{messages}};
  44         15576  
605 11         25 return $self->message(shift @{$self->{messages}})->message;
  11         46  
606             }
607              
608             1;
609              
610             =encoding utf8
611              
612             =head1 NAME
613              
614             Test2::MojoX - Testing Mojo
615              
616             =head1 SYNOPSIS
617              
618             use Test2::MojoX;
619             use Test2::V0;
620              
621             my $t = Test2::Mojo->new('MyApp');
622              
623             # HTML/XML
624             $t->get_ok('/welcome')->status_is(200)->text_is('div#message' => 'Hello!');
625              
626             # JSON
627             $t->post_ok('/search.json' => form => {q => 'Perl'})
628             ->status_is(200)
629             ->header_is('Server' => 'Mojolicious (Perl)')
630             ->header_isnt('X-Bender' => 'Bite my shiny metal ass!')
631             ->json_is('/results/4/title' => 'Perl rocks!')
632             ->json_like('/results/7/title' => qr/Perl/);
633              
634             # WebSocket
635             $t->websocket_ok('/echo')
636             ->send_ok('hello')
637             ->message_ok
638             ->message_is('echo: hello')
639             ->finish_ok;
640              
641             done_testing();
642              
643             =head1 DESCRIPTION
644              
645             L is a test user agent based on L, it is usually
646             used together with L to test L applications. Just run
647             your tests with L or L.
648              
649             $ yath -l -v
650             $ yath -l -v t/foo.t
651              
652             This package is fork of L. It was intended as a drop-in replacement for L
653             which can be used with L instead of L and L. This module
654             uses Test2 semantic for B and B. B for strict checks and B for relaxed. See
655             L for details.
656              
657             If it is not already defined, the C environment variable will
658             be set to C or C, depending on the value of the
659             C environment variable. And to make it easier to test
660             HTTPS/WSS web services L will be activated by
661             default for L.
662              
663             See L for more.
664              
665             =head1 ATTRIBUTES
666              
667             L implements the following attributes.
668              
669             =head2 message
670              
671             my $msg = $t->message;
672             $t = $t->message([text => $bytes]);
673              
674             Current WebSocket message represented as an array reference containing the
675             frame type and payload.
676              
677             # More specific tests
678             use Mojo::JSON 'decode_json';
679             my $hash = decode_json $t->message->[1];
680             is ref $hash, 'HASH', 'right reference';
681             is $hash->{foo}, 'bar', 'right value';
682              
683             # Test custom message
684             $t->message([binary => $bytes])
685             ->json_message_has('/foo/bar')
686             ->json_message_hasnt('/bar')
687             ->json_message_is('/foo/baz' => {yada => [1, 2, 3]});
688              
689             =head2 success
690              
691             my $bool = $t->success;
692             $t = $t->success($bool);
693              
694             True if the last test was successful.
695              
696             # Build custom tests
697             my $location_is = sub {
698             my ($t, $value, $desc) = @_;
699             $desc ||= "Location: $value";
700             return $t->success(is($t->tx->res->headers->location, $value, $desc));
701             };
702             $t->get_ok('/')
703             ->status_is(302)
704             ->$location_is('https://mojolicious.org')
705             ->or(sub { diag 'Must have been Joel!' });
706              
707             =head2 tx
708              
709             my $tx = $t->tx;
710             $t = $t->tx(Mojo::Transaction::HTTP->new);
711              
712             Current transaction, usually a L or
713             L object.
714              
715             # More specific tests
716             is $t->tx->res->json->{foo}, 'bar', 'right value';
717             ok $t->tx->res->content->is_multipart, 'multipart content';
718             is $t->tx->previous->res->code, 302, 'right status';
719              
720             =head2 ua
721              
722             my $ua = $t->ua;
723             $t = $t->ua(Mojo::UserAgent->new);
724              
725             User agent used for testing, defaults to a L object.
726              
727             # Allow redirects
728             $t->ua->max_redirects(10);
729             $t->get_ok('/redirect')->status_is(200)->content_like(qr/redirected/);
730              
731             # Switch protocol from HTTP to HTTPS
732             $t->ua->server->url('https');
733             $t->get_ok('/secure')->status_is(200)->content_like(qr/secure/);
734              
735             # Use absolute URL for request with Basic authentication
736             my $url = $t->ua->server->url->userinfo('sri:secr3t')->path('/secrets.json');
737             $t->post_ok($url => json => {limit => 10})
738             ->status_is(200)
739             ->json_is('/1/content', 'Mojo rocks!');
740              
741             # Customize all transactions (including followed redirects)
742             $t->ua->on(start => sub {
743             my ($ua, $tx) = @_;
744             $tx->req->headers->accept_language('en-US');
745             });
746             $t->get_ok('/hello')->status_is(200)->content_like(qr/Howdy/);
747              
748             =head1 METHODS
749              
750             L inherits all methods from L and implements the
751             following new ones.
752              
753             =head2 app
754              
755             my $app = $t->app;
756             $t = $t->app(Mojolicious->new);
757              
758             Access application with L.
759              
760             # Change log level
761             $t->app->log->level('fatal');
762              
763             # Test application directly
764             is $t->app->defaults->{foo}, 'bar', 'right value';
765             ok $t->app->routes->find('echo')->is_websocket, 'WebSocket route';
766             my $c = $t->app->build_controller;
767             ok $c->render(template => 'foo'), 'rendering was successful';
768             is $c->res->status, 200, 'right status';
769             is $c->res->body, 'Foo!', 'right content';
770              
771             # Change application behavior
772             $t->app->hook(before_dispatch => sub {
773             my $c = shift;
774             $c->render(text => 'This request did not reach the router.')
775             if $c->req->url->path->contains('/user');
776             });
777             $t->get_ok('/user')->status_is(200)->content_like(qr/not reach the router/);
778              
779             # Extract additional information
780             my $stash;
781             $t->app->hook(after_dispatch => sub { $stash = shift->stash });
782             $t->get_ok('/hello')->status_is(200);
783             is $stash->{foo}, 'bar', 'right value';
784              
785             =head2 attr_is
786              
787             $t = $t->attr_is('img.cat', 'alt', 'Grumpy cat');
788             $t = $t->attr_is('img.cat', 'alt', 'Grumpy cat', 'right alt text');
789              
790             Checks text content of attribute with L at the CSS selectors
791             first matching HTML/XML element for exact match with L.
792              
793             =head2 attr_isnt
794              
795             $t = $t->attr_isnt('img.cat', 'alt', 'Calm cat');
796             $t = $t->attr_isnt('img.cat', 'alt', 'Calm cat', 'different alt text');
797              
798             Opposite of L.
799              
800             =head2 attr_like
801              
802             $t = $t->attr_like('img.cat', 'alt', qr/Grumpy/);
803             $t = $t->attr_like('img.cat', 'alt', qr/Grumpy/, 'right alt text');
804              
805             Checks text content of attribute with L at the CSS selectors
806             first matching HTML/XML element for similar match with L.
807              
808             =head2 attr_unlike
809              
810             $t = $t->attr_unlike('img.cat', 'alt', qr/Calm/);
811             $t = $t->attr_unlike('img.cat', 'alt', qr/Calm/, 'different alt text');
812              
813             Opposite of L.
814              
815             =head2 content_is
816              
817             $t = $t->content_is('working!');
818             $t = $t->content_is('working!', 'right content');
819              
820             Check response content for exact match after retrieving it from
821             L.
822              
823             =head2 content_isnt
824              
825             $t = $t->content_isnt('working!');
826             $t = $t->content_isnt('working!', 'different content');
827              
828             Opposite of L.
829              
830             =head2 content_like
831              
832             $t = $t->content_like(qr/working!/);
833             $t = $t->content_like(qr/working!/, 'right content');
834              
835             Check response content for similar match after retrieving it from
836             L.
837              
838             =head2 content_type_is
839              
840             $t = $t->content_type_is('text/html');
841             $t = $t->content_type_is('text/html', 'right content type');
842              
843             Check response C header for exact match.
844              
845             =head2 content_type_isnt
846              
847             $t = $t->content_type_isnt('text/html');
848             $t = $t->content_type_isnt('text/html', 'different content type');
849              
850             Opposite of L.
851              
852             =head2 content_type_like
853              
854             $t = $t->content_type_like(qr/text/);
855             $t = $t->content_type_like(qr/text/, 'right content type');
856              
857             Check response C header for similar match.
858              
859             =head2 content_type_unlike
860              
861             $t = $t->content_type_unlike(qr/text/);
862             $t = $t->content_type_unlike(qr/text/, 'different content type');
863              
864             Opposite of L.
865              
866             =head2 content_unlike
867              
868             $t = $t->content_unlike(qr/working!/);
869             $t = $t->content_unlike(qr/working!/, 'different content');
870              
871             Opposite of L.
872              
873             =head2 delete_ok
874              
875             $t = $t->delete_ok('http://example.com/foo');
876             $t = $t->delete_ok('/foo');
877             $t = $t->delete_ok('/foo' => {Accept => '*/*'} => 'Content!');
878             $t = $t->delete_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
879             $t = $t->delete_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
880              
881             Perform a C request and check for transport errors, takes the same
882             arguments as L, except for the callback.
883              
884             =head2 element_count_is
885              
886             $t = $t->element_count_is('div.foo[x=y]', 5);
887             $t = $t->element_count_is('html body div', 30, 'thirty elements');
888              
889             Checks the number of HTML/XML elements matched by the CSS selector with
890             L.
891              
892             =head2 element_exists
893              
894             $t = $t->element_exists('div.foo[x=y]');
895             $t = $t->element_exists('html head title', 'has a title');
896              
897             Checks for existence of the CSS selectors first matching HTML/XML element with
898             L.
899              
900             # Check attribute values
901             $t->get_ok('/login')
902             ->element_exists('label[for=email]')
903             ->element_exists('input[name=email][type=text][value*="example.com"]')
904             ->element_exists('label[for=pass]')
905             ->element_exists('input[name=pass][type=password]')
906             ->element_exists('input[type=submit][value]');
907              
908             =head2 element_exists_not
909              
910             $t = $t->element_exists_not('div.foo[x=y]');
911             $t = $t->element_exists_not('html head title', 'has no title');
912              
913             Opposite of L.
914              
915             =head2 finish_ok
916              
917             $t = $t->finish_ok;
918             $t = $t->finish_ok(1000);
919             $t = $t->finish_ok(1003 => 'Cannot accept data!');
920              
921             Close WebSocket connection gracefully.
922              
923             =head2 finished_ok
924              
925             $t = $t->finished_ok(1000);
926              
927             Wait for WebSocket connection to be closed gracefully and check status.
928              
929             =head2 get_ok
930              
931             $t = $t->get_ok('http://example.com/foo');
932             $t = $t->get_ok('/foo');
933             $t = $t->get_ok('/foo' => {Accept => '*/*'} => 'Content!');
934             $t = $t->get_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
935             $t = $t->get_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
936              
937             Perform a C request and check for transport errors, takes the same
938             arguments as L, except for the callback.
939              
940             # Run tests against remote host
941             $t->get_ok('https://mojolicious.org/perldoc')->status_is(200);
942              
943             # Use relative URL for request with Basic authentication
944             $t->get_ok('//sri:secr3t@/secrets.json')
945             ->status_is(200)
946             ->json_is('/1/content', 'Mojo rocks!');
947              
948             # Run additional tests on the transaction
949             $t->get_ok('/foo')->status_is(200);
950             is $t->tx->res->dom->at('input')->val, 'whatever', 'right value';
951              
952             =head2 head_ok
953              
954             $t = $t->head_ok('http://example.com/foo');
955             $t = $t->head_ok('/foo');
956             $t = $t->head_ok('/foo' => {Accept => '*/*'} => 'Content!');
957             $t = $t->head_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
958             $t = $t->head_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
959              
960             Perform a C request and check for transport errors, takes the same
961             arguments as L, except for the callback.
962              
963             =head2 header_exists
964              
965             $t = $t->header_exists('ETag');
966             $t = $t->header_exists('ETag', 'header exists');
967              
968             Check if response header exists.
969              
970             =head2 header_exists_not
971              
972             $t = $t->header_exists_not('ETag');
973             $t = $t->header_exists_not('ETag', 'header is missing');
974              
975             Opposite of L.
976              
977             =head2 header_is
978              
979             $t = $t->header_is(ETag => '"abc321"');
980             $t = $t->header_is(ETag => '"abc321"', 'right header');
981              
982             Check response header for exact match.
983              
984             =head2 header_isnt
985              
986             $t = $t->header_isnt(Etag => '"abc321"');
987             $t = $t->header_isnt(ETag => '"abc321"', 'different header');
988              
989             Opposite of L.
990              
991             =head2 header_like
992              
993             $t = $t->header_like(ETag => qr/abc/);
994             $t = $t->header_like(ETag => qr/abc/, 'right header');
995              
996             Check response header for similar match.
997              
998             =head2 header_unlike
999              
1000             $t = $t->header_unlike(ETag => qr/abc/);
1001             $t = $t->header_unlike(ETag => qr/abc/, 'different header');
1002              
1003             Opposite of L.
1004              
1005             =head2 json_has
1006              
1007             $t = $t->json_has('/foo');
1008             $t = $t->json_has('/minibar', 'has a minibar');
1009              
1010             Check if JSON response contains a value that can be identified using the given
1011             JSON Pointer with L.
1012              
1013             =head2 json_hasnt
1014              
1015             $t = $t->json_hasnt('/foo');
1016             $t = $t->json_hasnt('/minibar', 'no minibar');
1017              
1018             Opposite of L.
1019              
1020             =head2 json_is
1021              
1022             $t = $t->json_is({foo => [1, 2, 3]});
1023             $t = $t->json_is('/foo' => [1, 2, 3]);
1024             $t = $t->json_is('/foo/1' => 2, 'right value');
1025             $t = $t->json_is(hash {
1026             field foo => array {
1027             item 1;
1028             item 2;
1029             item 3;
1030             end;
1031             };
1032             end;
1033             });
1034              
1035             Check the value extracted from JSON response using the given JSON Pointer with
1036             L, which defaults to the root value if it is omitted.
1037              
1038             =head2 json_like
1039              
1040             $t = $t->json_like('/foo/1' => qr/^\d+$/);
1041             $t = $t->json_like('/foo/1' => qr/^\d+$/, 'right value');
1042             $t = $t->json_like(hash {
1043             field foo => hash {
1044             field 1 => D;
1045             };
1046             etc;
1047             });
1048              
1049             Check the value extracted from JSON response using the given JSON Pointer with
1050             L for similar match.
1051              
1052             =head2 json_message_has
1053              
1054             $t = $t->json_message_has('/foo');
1055             $t = $t->json_message_has('/minibar', 'has a minibar');
1056              
1057             Check if JSON WebSocket message contains a value that can be identified using
1058             the given JSON Pointer with L.
1059              
1060             =head2 json_message_hasnt
1061              
1062             $t = $t->json_message_hasnt('/foo');
1063             $t = $t->json_message_hasnt('/minibar', 'no minibar');
1064              
1065             Opposite of L.
1066              
1067             =head2 json_message_is
1068              
1069             $t = $t->json_message_is({foo => [1, 2, 3]});
1070             $t = $t->json_message_is('/foo' => [1, 2, 3]);
1071             $t = $t->json_message_is('/foo/1' => 2, 'right value');
1072             $t = $t->json_message_is(hash {
1073             field foo => array {
1074             item 1;
1075             item 2;
1076             item 3;
1077             end;
1078             };
1079             end;
1080             });
1081              
1082             Check the value extracted from JSON WebSocket message using the given JSON
1083             Pointer with L, which defaults to the root value if it is
1084             omitted.
1085              
1086             =head2 json_message_like
1087              
1088             $t = $t->json_message_like('/foo/1' => qr/^\d+$/);
1089             $t = $t->json_message_like('/foo/1' => qr/^\d+$/, 'right value');
1090             $t = $t->json_message_like(hash {
1091             field foo => bag {
1092             item 2;
1093             etc;
1094             };
1095             });
1096              
1097             Check the value extracted from JSON WebSocket message using the given JSON
1098             Pointer with L for similar match, which defaults to
1099             the root value if it is omitted.
1100              
1101             =head2 json_message_unlike
1102              
1103             $t = $t->json_message_unlike('/foo/1' => qr/^\d+$/);
1104             $t = $t->json_message_unlike('/foo/1' => qr/^\d+$/, 'different value');
1105             $t = $t->json_message_unlike(hash {
1106             field foo => bag {
1107             item 1;
1108             etc;
1109             };
1110             });
1111              
1112             Opposite of L.
1113              
1114             =head2 json_unlike
1115              
1116             $t = $t->json_unlike('/foo/1' => qr/^\d+$/);
1117             $t = $t->json_unlike('/foo/1' => qr/^\d+$/, 'different value');
1118             $t = $t->json_unlike(hash {
1119             field foo => bag {
1120             item 1;
1121             etc;
1122             };
1123             });
1124              
1125             Opposite of L.
1126              
1127             =head2 message_is
1128              
1129             $t = $t->message_is({binary => $bytes});
1130             $t = $t->message_is({text => $bytes});
1131             $t = $t->message_is('working!');
1132             $t = $t->message_is('working!', 'right message');
1133              
1134             Check WebSocket message for exact match.
1135              
1136             =head2 message_isnt
1137              
1138             $t = $t->message_isnt({binary => $bytes});
1139             $t = $t->message_isnt({text => $bytes});
1140             $t = $t->message_isnt('working!');
1141             $t = $t->message_isnt('working!', 'different message');
1142              
1143             Opposite of L.
1144              
1145             =head2 message_like
1146              
1147             $t = $t->message_like({binary => qr/$bytes/});
1148             $t = $t->message_like({text => qr/$bytes/});
1149             $t = $t->message_like(qr/working!/);
1150             $t = $t->message_like(qr/working!/, 'right message');
1151              
1152             Check WebSocket message for similar match.
1153              
1154             =head2 message_ok
1155              
1156             $t = $t->message_ok;
1157             $t = $t->message_ok('got a message');
1158              
1159             Wait for next WebSocket message to arrive.
1160              
1161             # Wait for message and perform multiple tests on it
1162             $t->websocket_ok('/time')
1163             ->message_ok
1164             ->message_like(qr/\d+/)
1165             ->message_unlike(qr/\w+/)
1166             ->finish_ok;
1167              
1168             =head2 message_unlike
1169              
1170             $t = $t->message_unlike({binary => qr/$bytes/});
1171             $t = $t->message_unlike({text => qr/$bytes/});
1172             $t = $t->message_unlike(qr/working!/);
1173             $t = $t->message_unlike(qr/working!/, 'different message');
1174              
1175             Opposite of L.
1176              
1177             =head2 new
1178              
1179             my $t = Test2::Mojo->new;
1180             my $t = Test2::Mojo->new('MyApp');
1181             my $t = Test2::Mojo->new('MyApp', {foo => 'bar'});
1182             my $t = Test2::Mojo->new(Mojo::File->new('/path/to/myapp.pl'));
1183             my $t = Test2::Mojo->new(Mojo::File->new('/path/to/myapp.pl'), {foo => 'bar'});
1184             my $t = Test2::Mojo->new(MyApp->new);
1185             my $t = Test2::Mojo->new(MyApp->new, {foo => 'bar'});
1186              
1187             Construct a new L object. In addition to a class name or
1188             L object pointing to the application script, you can pass along a
1189             hash reference with configuration values that will be used to override the
1190             application configuration. The special configuration value C
1191             will be set in L as well, which is used to disable
1192             configuration plugins like L and
1193             L for tests.
1194              
1195             # Load application script relative to the "t" directory
1196             use Mojo::File 'path';
1197             my $t = Test2::Mojo->new(path(__FILE__)->dirname->sibling('myapp.pl'));
1198              
1199             =head2 options_ok
1200              
1201             $t = $t->options_ok('http://example.com/foo');
1202             $t = $t->options_ok('/foo');
1203             $t = $t->options_ok('/foo' => {Accept => '*/*'} => 'Content!');
1204             $t = $t->options_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
1205             $t = $t->options_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
1206              
1207             Perform a C request and check for transport errors, takes the same
1208             arguments as L, except for the callback.
1209              
1210             =head2 or
1211              
1212             $t = $t->or(sub {...});
1213              
1214             Execute callback if the value of L is false.
1215              
1216             # Diagnostics
1217             $t->get_ok('/bad')->or(sub { diag 'Must have been Glen!' })
1218             ->status_is(200)->or(sub { diag $t->tx->res->dom->at('title')->text });
1219              
1220             =head2 patch_ok
1221              
1222             $t = $t->patch_ok('http://example.com/foo');
1223             $t = $t->patch_ok('/foo');
1224             $t = $t->patch_ok('/foo' => {Accept => '*/*'} => 'Content!');
1225             $t = $t->patch_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
1226             $t = $t->patch_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
1227              
1228             Perform a C request and check for transport errors, takes the same
1229             arguments as L, except for the callback.
1230              
1231             =head2 post_ok
1232              
1233             $t = $t->post_ok('http://example.com/foo');
1234             $t = $t->post_ok('/foo');
1235             $t = $t->post_ok('/foo' => {Accept => '*/*'} => 'Content!');
1236             $t = $t->post_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
1237             $t = $t->post_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
1238              
1239             Perform a C request and check for transport errors, takes the same
1240             arguments as L, except for the callback.
1241              
1242             # Test file upload
1243             my $upload = {foo => {content => 'bar', filename => 'baz.txt'}};
1244             $t->post_ok('/upload' => form => $upload)->status_is(200);
1245              
1246             # Test JSON API
1247             $t->post_ok('/hello.json' => json => {hello => 'world'})
1248             ->status_is(200)
1249             ->json_is({bye => 'world'});
1250              
1251             =head2 put_ok
1252              
1253             $t = $t->put_ok('http://example.com/foo');
1254             $t = $t->put_ok('/foo');
1255             $t = $t->put_ok('/foo' => {Accept => '*/*'} => 'Content!');
1256             $t = $t->put_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
1257             $t = $t->put_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
1258              
1259             Perform a C request and check for transport errors, takes the same
1260             arguments as L, except for the callback.
1261              
1262             =head2 request_ok
1263              
1264             $t = $t->request_ok(Mojo::Transaction::HTTP->new);
1265              
1266             Perform request and check for transport errors.
1267              
1268             # Request with custom method
1269             my $tx = $t->ua->build_tx(FOO => '/test.json' => json => {foo => 1});
1270             $t->request_ok($tx)->status_is(200)->json_is({success => 1});
1271              
1272             # Request with custom cookie
1273             my $tx = $t->ua->build_tx(GET => '/account');
1274             $tx->req->cookies({name => 'user', value => 'sri'});
1275             $t->request_ok($tx)->status_is(200)->text_is('head > title' => 'Hello sri');
1276              
1277             # Custom WebSocket handshake
1278             my $tx = $t->ua->build_websocket_tx('/foo');
1279             $tx->req->headers->remove('User-Agent');
1280             $t->request_ok($tx)->message_ok->message_is('bar')->finish_ok;
1281              
1282             =head2 reset_session
1283              
1284             $t = $t->reset_session;
1285              
1286             Reset user agent session.
1287              
1288             =head2 send_ok
1289              
1290             $t = $t->send_ok({binary => $bytes});
1291             $t = $t->send_ok({text => $bytes});
1292             $t = $t->send_ok({json => {test => [1, 2, 3]}});
1293             $t = $t->send_ok([$fin, $rsv1, $rsv2, $rsv3, $op, $payload]);
1294             $t = $t->send_ok($chars);
1295             $t = $t->send_ok($chars, 'sent successfully');
1296              
1297             Send message or frame via WebSocket.
1298              
1299             # Send JSON object as "Text" message
1300             $t->websocket_ok('/echo.json')
1301             ->send_ok({json => {test => 'I ♥ Mojolicious!'}})
1302             ->message_ok
1303             ->json_message_is('/test' => 'I ♥ Mojolicious!')
1304             ->finish_ok;
1305              
1306             =head2 status_is
1307              
1308             $t = $t->status_is(200);
1309             $t = $t->status_is(200, 'right status');
1310             $t = $t->status_is(match qr/^3/, 'request redirected');
1311              
1312             Check response status for exact match.
1313              
1314             =head2 status_isnt
1315              
1316             $t = $t->status_isnt(200);
1317             $t = $t->status_isnt(200, 'different status');
1318             $t = $t->status_isnt(match qr/^3/, 'request is not redirected');
1319              
1320             Opposite of L.
1321              
1322             =head2 text_is
1323              
1324             $t = $t->text_is('div.foo[x=y]' => 'Hello!');
1325             $t = $t->text_is('html head title' => 'Hello!', 'right title');
1326              
1327             Checks text content of the CSS selectors first matching HTML/XML element for
1328             exact match with L.
1329              
1330             =head2 text_isnt
1331              
1332             $t = $t->text_isnt('div.foo[x=y]' => 'Hello!');
1333             $t = $t->text_isnt('html head title' => 'Hello!', 'different title');
1334              
1335             Opposite of L.
1336              
1337             =head2 text_like
1338              
1339             $t = $t->text_like('div.foo[x=y]' => qr/Hello/);
1340             $t = $t->text_like('html head title' => qr/Hello/, 'right title');
1341              
1342             Checks text content of the CSS selectors first matching HTML/XML element for
1343             similar match with L.
1344              
1345             =head2 text_unlike
1346              
1347             $t = $t->text_unlike('div.foo[x=y]' => qr/Hello/);
1348             $t = $t->text_unlike('html head title' => qr/Hello/, 'different title');
1349              
1350             Opposite of L.
1351              
1352             =head2 websocket_ok
1353              
1354             $t = $t->websocket_ok('http://example.com/echo');
1355             $t = $t->websocket_ok('/echo');
1356             $t = $t->websocket_ok('/echo' => {DNT => 1} => ['v1.proto']);
1357              
1358             Open a WebSocket connection with transparent handshake, takes the same
1359             arguments as L, except for the callback.
1360              
1361             # WebSocket with permessage-deflate compression
1362             $t->websocket_ok('/' => {'Sec-WebSocket-Extensions' => 'permessage-deflate'})
1363             ->send_ok('y' x 50000)
1364             ->message_ok
1365             ->message_is('z' x 50000)
1366             ->finish_ok;
1367              
1368             =head1 ACKNOWLEDGEMENTS
1369              
1370             This code was pretty much taken directly from L:
1371              
1372             Sebastian Riedel C and other wonderful people who contributed to L.
1373              
1374             Chad Granum C for his outstanding work on L and other
1375             perl testing tools and for advices on the code.
1376              
1377             José Joaquín Atria C for the idea to put L
1378             and L together.
1379              
1380             =head1 COPYRIGHT AND LICENSE
1381              
1382             Copyright (C) 2019-2021, Ilya Rassadin Eelcamlost@cpan.orgE.
1383              
1384             This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version
1385             2.0.
1386              
1387              
1388             =head1 AUTHORS
1389              
1390             =over 4
1391              
1392             =item Ilya Rassadin Eelcamlost@cpan.orgE
1393              
1394             =back
1395              
1396             =head1 SEE ALSO
1397              
1398             L, L, L, L.
1399              
1400             =cut