]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - tests.js
Entropy with more than 4 decks can be calculated
[perso/Immae/Projets/Cryptomonnaies/BIP39.git] / tests.js
1 // Usage:
2 // $ phantomjs tests.js
3
4
5 var page = require('webpage').create();
6 var url = 'src/index.html';
7 var testMaxTime = 10000;
8
9 page.onResourceError = function(e) {
10 console.log("Error loading " + e.url);
11 phantom.exit();
12 }
13
14 function fail() {
15 console.log("Failed");
16 phantom.exit();
17 }
18
19 function waitForGenerate(fn, maxTime) {
20 if (!maxTime) {
21 maxTime = testMaxTime;
22 }
23 var start = new Date().getTime();
24 var prevAddressCount = -1;
25 var wait = function keepWaiting() {
26 var now = new Date().getTime();
27 var hasTimedOut = now - start > maxTime;
28 var addressCount = page.evaluate(function() {
29 return $(".address").length;
30 });
31 var hasFinished = addressCount > 0 && addressCount == prevAddressCount;
32 prevAddressCount = addressCount;
33 if (hasFinished) {
34 fn();
35 }
36 else if (hasTimedOut) {
37 console.log("Test timed out");
38 fn();
39 }
40 else {
41 setTimeout(keepWaiting, 100);
42 }
43 }
44 wait();
45 }
46
47 function waitForFeedback(fn, maxTime) {
48 if (!maxTime) {
49 maxTime = testMaxTime;
50 }
51 var start = new Date().getTime();
52 var wait = function keepWaiting() {
53 var now = new Date().getTime();
54 var hasTimedOut = now - start > maxTime;
55 if (hasTimedOut) {
56 console.log("Test timed out");
57 fn();
58 return;
59 }
60 var feedback = page.evaluate(function() {
61 var feedback = $(".feedback");
62 if (feedback.css("display") == "none") {
63 return "";
64 }
65 return feedback.text();
66 });
67 var hasFinished = feedback.length > 0 && feedback != "Calculating...";
68 if (hasFinished) {
69 fn();
70 }
71 else {
72 setTimeout(keepWaiting, 100);
73 }
74 }
75 wait();
76 }
77
78 function waitForEntropyFeedback(fn, maxTime) {
79 if (!maxTime) {
80 maxTime = testMaxTime;
81 }
82 var origFeedback = page.evaluate(function() {
83 return $(".entropy-container").text();
84 });
85 var start = new Date().getTime();
86 var wait = function keepWaiting() {
87 var now = new Date().getTime();
88 var hasTimedOut = now - start > maxTime;
89 if (hasTimedOut) {
90 console.log("Test timed out");
91 fn();
92 return;
93 }
94 var feedback = page.evaluate(function() {
95 return $(".entropy-container").text();
96 });
97 var hasFinished = feedback != origFeedback;
98 if (hasFinished) {
99 fn();
100 }
101 else {
102 setTimeout(keepWaiting, 100);
103 }
104 }
105 wait();
106 }
107
108 function next() {
109 if (tests.length > 0) {
110 var testsStr = tests.length == 1 ? "test" : "tests";
111 console.log(tests.length + " " + testsStr + " remaining");
112 tests.shift()();
113 }
114 else {
115 console.log("Finished with 0 failures");
116 phantom.exit();
117 }
118 }
119
120 /**
121 * Randomize array element order in-place.
122 * Using Durstenfeld shuffle algorithm.
123 * See http://stackoverflow.com/a/12646864
124 */
125 function shuffle(array) {
126 for (var i = array.length - 1; i > 0; i--) {
127 var j = Math.floor(Math.random() * (i + 1));
128 var temp = array[i];
129 array[i] = array[j];
130 array[j] = temp;
131 }
132 return array;
133 }
134
135 tests = [
136
137 // Page loads with status of 'success'
138 function() {
139 page.open(url, function(status) {
140 if (status != "success") {
141 console.log("Page did not load with status 'success'");
142 fail();
143 }
144 next();
145 });
146 },
147
148 // Page has text
149 function() {
150 page.open(url, function(status) {
151 var content = page.evaluate(function() {
152 return document.body.textContent.trim();
153 });
154 if (!content) {
155 console.log("Page does not have text");
156 fail();
157 }
158 next();
159 });
160 },
161
162 // Entering mnemonic generates addresses
163 function() {
164 page.open(url, function(status) {
165 // set the phrase
166 page.evaluate(function() {
167 $(".phrase").val("abandon abandon ability").trigger("input");
168 });
169 // get the address
170 waitForGenerate(function() {
171 var addressCount = page.evaluate(function() {
172 return $(".address").length;
173 });
174 if (addressCount != 20) {
175 console.log("Mnemonic did not generate addresses");
176 console.log("Expected: " + expected);
177 console.log("Got: " + actual);
178 fail();
179 }
180 next();
181 });
182 });
183 },
184
185 // Random button generates random mnemonic
186 function() {
187 page.open(url, function(status) {
188 // check initial phrase is empty
189 var phrase = page.evaluate(function() {
190 return $(".phrase").text();
191 });
192 if (phrase != "") {
193 console.log("Initial phrase is not blank");
194 fail();
195 }
196 // press the 'generate' button
197 page.evaluate(function() {
198 $(".generate").click();
199 });
200 // get the new phrase
201 waitForGenerate(function() {
202 var phrase = page.evaluate(function() {
203 return $(".phrase").val();
204 });
205 if (phrase.length <= 0) {
206 console.log("Phrase not generated by pressing button");
207 fail();
208 }
209 next();
210 });
211 });
212 },
213
214 // Mnemonic length can be customized
215 function() {
216 page.open(url, function(status) {
217 // set the length to 6
218 var expectedLength = "6";
219 page.evaluate(function() {
220 $(".strength option[selected]").removeAttr("selected");
221 $(".strength option[value=6]").prop("selected", true);
222 });
223 // press the 'generate' button
224 page.evaluate(function() {
225 $(".generate").click();
226 });
227 // check the new phrase is six words long
228 waitForGenerate(function() {
229 var actualLength = page.evaluate(function() {
230 var words = $(".phrase").val().split(" ");
231 return words.length;
232 });
233 if (actualLength != expectedLength) {
234 console.log("Phrase not generated with correct length");
235 console.log("Expected: " + expectedLength);
236 console.log("Actual: " + actualLength);
237 fail();
238 }
239 next();
240 });
241 });
242 },
243
244 // Passphrase can be set
245 function() {
246 page.open(url, function(status) {
247 // set the phrase and passphrase
248 var expected = "15pJzUWPGzR7avffV9nY5by4PSgSKG9rba";
249 page.evaluate(function() {
250 $(".phrase").val("abandon abandon ability");
251 $(".passphrase").val("secure_passphrase").trigger("input");
252 });
253 // check the address is generated correctly
254 waitForGenerate(function() {
255 var actual = page.evaluate(function() {
256 return $(".address:first").text();
257 });
258 if (actual != expected) {
259 console.log("Passphrase results in wrong address");
260 console.log("Expected: " + expected);
261 console.log("Actual: " + actual);
262 fail();
263 }
264 next();
265 });
266 });
267 },
268
269 // Network can be set to bitcoin testnet
270 function() {
271 page.open(url, function(status) {
272 // set the phrase and coin
273 var expected = "mucaU5iiDaJDb69BHLeDv8JFfGiyg2nJKi";
274 page.evaluate(function() {
275 $(".phrase").val("abandon abandon ability");
276 $(".phrase").trigger("input");
277 $(".network option[selected]").removeAttr("selected");
278 $(".network option[value=1]").prop("selected", true);
279 $(".network").trigger("change");
280 });
281 // check the address is generated correctly
282 waitForGenerate(function() {
283 var actual = page.evaluate(function() {
284 return $(".address:first").text();
285 });
286 if (actual != expected) {
287 console.log("Bitcoin testnet address is incorrect");
288 console.log("Expected: " + expected);
289 console.log("Actual: " + actual);
290 fail();
291 }
292 next();
293 });
294 });
295 },
296
297 // Network can be set to litecoin
298 function() {
299 page.open(url, function(status) {
300 // set the phrase and coin
301 var expected = "LQ4XU8RX2ULPmPq9FcUHdVmPVchP9nwXdn";
302 page.evaluate(function() {
303 $(".phrase").val("abandon abandon ability");
304 $(".phrase").trigger("input");
305 $(".network option[selected]").removeAttr("selected");
306 $(".network option[value=2]").prop("selected", true);
307 $(".network").trigger("change");
308 });
309 // check the address is generated correctly
310 waitForGenerate(function() {
311 var actual = page.evaluate(function() {
312 return $(".address:first").text();
313 });
314 if (actual != expected) {
315 console.log("Litecoin address is incorrect");
316 console.log("Expected: " + expected);
317 console.log("Actual: " + actual);
318 fail();
319 }
320 next();
321 });
322 });
323 },
324
325 // Network can be set to dogecoin
326 function() {
327 page.open(url, function(status) {
328 // set the phrase and coin
329 var expected = "DPQH2AtuzkVSG6ovjKk4jbUmZ6iXLpgbJA";
330 page.evaluate(function() {
331 $(".phrase").val("abandon abandon ability");
332 $(".phrase").trigger("input");
333 $(".network option[selected]").removeAttr("selected");
334 $(".network option[value=3]").prop("selected", true);
335 $(".network").trigger("change");
336 });
337 // check the address is generated correctly
338 waitForGenerate(function() {
339 var actual = page.evaluate(function() {
340 return $(".address:first").text();
341 });
342 if (actual != expected) {
343 console.log("Dogecoin address is incorrect");
344 console.log("Expected: " + expected);
345 console.log("Actual: " + actual);
346 fail();
347 }
348 next();
349 });
350 });
351 },
352
353 // Network can be set to shadowcash
354 function() {
355 page.open(url, function(status) {
356 // set the phrase and coin
357 var expected = "SiSZtfYAXEFvMm3XM8hmtkGDyViRwErtCG";
358 page.evaluate(function() {
359 $(".phrase").val("abandon abandon ability");
360 $(".phrase").trigger("input");
361 $(".network option[selected]").removeAttr("selected");
362 $(".network option[value=4]").prop("selected", true);
363 $(".network").trigger("change");
364 });
365 // check the address is generated correctly
366 waitForGenerate(function() {
367 var actual = page.evaluate(function() {
368 return $(".address:first").text();
369 });
370 if (actual != expected) {
371 console.log("Shadowcash address is incorrect");
372 console.log("Expected: " + expected);
373 console.log("Actual: " + actual);
374 fail();
375 }
376 next();
377 });
378 });
379 },
380
381 // Network can be set to shadowcash testnet
382 function() {
383 page.open(url, function(status) {
384 // set the phrase and coin
385 var expected = "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
386 page.evaluate(function() {
387 $(".phrase").val("abandon abandon ability");
388 $(".phrase").trigger("input");
389 $(".network option[selected]").removeAttr("selected");
390 $(".network option[value=5]").prop("selected", true);
391 $(".network").trigger("change");
392 });
393 // check the address is generated correctly
394 waitForGenerate(function() {
395 var actual = page.evaluate(function() {
396 return $(".address:first").text();
397 });
398 if (actual != expected) {
399 console.log("Shadowcash testnet address is incorrect");
400 console.log("Expected: " + expected);
401 console.log("Actual: " + actual);
402 fail();
403 }
404 next();
405 });
406 });
407 },
408
409 // Network can be set to viacoin
410 function() {
411 page.open(url, function(status) {
412 // set the phrase and coin
413 var expected = "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
414 page.evaluate(function() {
415 $(".phrase").val("abandon abandon ability");
416 $(".phrase").trigger("input");
417 $(".network option[selected]").removeAttr("selected");
418 $(".network option[value=6]").prop("selected", true);
419 $(".network").trigger("change");
420 });
421 // check the address is generated correctly
422 waitForGenerate(function() {
423 var actual = page.evaluate(function() {
424 return $(".address:first").text();
425 });
426 if (actual != expected) {
427 console.log("Viacoin address is incorrect");
428 console.log("Expected: " + expected);
429 console.log("Actual: " + actual);
430 fail();
431 }
432 next();
433 });
434 });
435 },
436
437 // Network can be set to viacoin testnet
438 function() {
439 page.open(url, function(status) {
440 // set the phrase and coin
441 var expected = "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
442 page.evaluate(function() {
443 $(".phrase").val("abandon abandon ability");
444 $(".phrase").trigger("input");
445 $(".network option[selected]").removeAttr("selected");
446 $(".network option[value=7]").prop("selected", true);
447 $(".network").trigger("change");
448 });
449 // check the address is generated correctly
450 waitForGenerate(function() {
451 var actual = page.evaluate(function() {
452 return $(".address:first").text();
453 });
454 if (actual != expected) {
455 console.log("Viacoin testnet address is incorrect");
456 console.log("Expected: " + expected);
457 console.log("Actual: " + actual);
458 fail();
459 }
460 next();
461 });
462 });
463 },
464
465 // Network can be set to jumbucks
466 function() {
467 page.open(url, function(status) {
468 // set the phrase and coin
469 var expected = "JLEXccwDXADK4RxBPkRez7mqsHVoJBEUew";
470 page.evaluate(function() {
471 $(".phrase").val("abandon abandon ability");
472 $(".phrase").trigger("input");
473 $(".network option[selected]").removeAttr("selected");
474 $(".network option[value=8]").prop("selected", true);
475 $(".network").trigger("change");
476 });
477 // check the address is generated correctly
478 waitForGenerate(function() {
479 var actual = page.evaluate(function() {
480 return $(".address:first").text();
481 });
482 if (actual != expected) {
483 console.log("Jumbucks address is incorrect");
484 console.log("Expected: " + expected);
485 console.log("Actual: " + actual);
486 fail();
487 }
488 next();
489 });
490 });
491 },
492
493 // Network can be set to clam
494 function() {
495 page.open(url, function(status) {
496 // set the phrase and coin
497 var expected = "xCp4sakjVx4pUAZ6cBCtuin8Ddb6U1sk9y";
498 page.evaluate(function() {
499 $(".phrase").val("abandon abandon ability");
500 $(".phrase").trigger("input");
501 $(".network option[selected]").removeAttr("selected");
502 $(".network option[value=9]").prop("selected", true);
503 $(".network").trigger("change");
504 });
505 // check the address is generated correctly
506 waitForGenerate(function() {
507 var actual = page.evaluate(function() {
508 return $(".address:first").text();
509 });
510 if (actual != expected) {
511 console.log("CLAM address is incorrect");
512 console.log("Expected: " + expected);
513 console.log("Actual: " + actual);
514 fail();
515 }
516 next();
517 });
518 });
519 },
520
521 // Network can be set to dash
522 function() {
523 page.open(url, function(status) {
524 // set the phrase and coin
525 var expected = "XdbhtMuGsPSkE6bPdNTHoFSszQKmK4S5LT";
526 page.evaluate(function() {
527 $(".phrase").val("abandon abandon ability");
528 $(".phrase").trigger("input");
529 $(".network option[selected]").removeAttr("selected");
530 $(".network option[value=10]").prop("selected", true);
531 $(".network").trigger("change");
532 });
533 // check the address is generated correctly
534 waitForGenerate(function() {
535 var actual = page.evaluate(function() {
536 return $(".address:first").text();
537 });
538 if (actual != expected) {
539 console.log("DASH address is incorrect");
540 console.log("Expected: " + expected);
541 console.log("Actual: " + actual);
542 fail();
543 }
544 next();
545 });
546 });
547 },
548
549 // Network can be set to namecoin
550 function() {
551 page.open(url, function(status) {
552 // set the phrase and coin
553 var expected = "Mw2vK2Bvex1yYtYF6sfbEg2YGoUc98YUD2";
554 page.evaluate(function() {
555 $(".phrase").val("abandon abandon ability");
556 $(".phrase").trigger("input");
557 $(".network option[selected]").removeAttr("selected");
558 $(".network option[value=11]").prop("selected", true);
559 $(".network").trigger("change");
560 });
561 // check the address is generated correctly
562 waitForGenerate(function() {
563 var actual = page.evaluate(function() {
564 return $(".address:first").text();
565 });
566 if (actual != expected) {
567 console.log("Namecoin address is incorrect");
568 console.log("Expected: " + expected);
569 console.log("Actual: " + actual);
570 fail();
571 }
572 next();
573 });
574 });
575 },
576
577 // Network can be set to peercoin
578 function() {
579 page.open(url, function(status) {
580 // set the phrase and coin
581 var expected = "PVAiioTaK2eDHSEo3tppT9AVdBYqxRTBAm";
582 page.evaluate(function() {
583 $(".phrase").val("abandon abandon ability");
584 $(".phrase").trigger("input");
585 $(".network option[selected]").removeAttr("selected");
586 $(".network option[value=12]").prop("selected", true);
587 $(".network").trigger("change");
588 });
589 // check the address is generated correctly
590 waitForGenerate(function() {
591 var actual = page.evaluate(function() {
592 return $(".address:first").text();
593 });
594 if (actual != expected) {
595 console.log("Peercoin address is incorrect");
596 console.log("Expected: " + expected);
597 console.log("Actual: " + actual);
598 fail();
599 }
600 next();
601 });
602 });
603 },
604
605 // BIP39 seed is set from phrase
606 function() {
607 page.open(url, function(status) {
608 // set the phrase
609 var expected = "20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3";
610 page.evaluate(function() {
611 $(".phrase").val("abandon abandon ability");
612 $(".phrase").trigger("input");
613 });
614 // check the address is generated correctly
615 waitForGenerate(function() {
616 var actual = page.evaluate(function() {
617 return $(".seed").val();
618 });
619 if (actual != expected) {
620 console.log("BIP39 seed is incorrectly generated from mnemonic");
621 console.log("Expected: " + expected);
622 console.log("Actual: " + actual);
623 fail();
624 }
625 next();
626 });
627 });
628 },
629
630 // BIP32 root key is set from phrase
631 function() {
632 page.open(url, function(status) {
633 // set the phrase
634 var expected = "xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi";
635 page.evaluate(function() {
636 $(".phrase").val("abandon abandon ability");
637 $(".phrase").trigger("input");
638 });
639 // check the address is generated correctly
640 waitForGenerate(function() {
641 var actual = page.evaluate(function() {
642 return $(".root-key").val();
643 });
644 if (actual != expected) {
645 console.log("Root key is incorrectly generated from mnemonic");
646 console.log("Expected: " + expected);
647 console.log("Actual: " + actual);
648 fail();
649 }
650 next();
651 });
652 });
653 },
654
655 // Tabs show correct addresses when changed
656 function() {
657 page.open(url, function(status) {
658 // set the phrase
659 var expected = "17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz";
660 page.evaluate(function() {
661 $(".phrase").val("abandon abandon ability");
662 $(".phrase").trigger("input");
663 });
664 // change tabs
665 waitForGenerate(function() {
666 page.evaluate(function() {
667 $("#bip32-tab a").click();
668 });
669 // check the address is generated correctly
670 waitForGenerate(function() {
671 var actual = page.evaluate(function() {
672 return $(".address:first").text();
673 });
674 if (actual != expected) {
675 console.log("Clicking tab generates incorrect address");
676 console.log("Expected: " + expected);
677 console.log("Actual: " + actual);
678 fail();
679 }
680 next();
681 });
682 });
683 });
684 },
685
686 // BIP44 derivation path is shown
687 function() {
688 page.open(url, function(status) {
689 // set the phrase
690 var expected = "m/44'/0'/0'/0";
691 page.evaluate(function() {
692 $(".phrase").val("abandon abandon ability");
693 $(".phrase").trigger("input");
694 });
695 // check the derivation path of the first address
696 waitForGenerate(function() {
697 var actual = page.evaluate(function() {
698 return $("#bip44 .path").val();
699 });
700 if (actual != expected) {
701 console.log("BIP44 derivation path is incorrect");
702 console.log("Expected: " + expected);
703 console.log("Actual: " + actual);
704 fail();
705 }
706 next();
707 });
708 });
709 },
710
711 // BIP44 extended private key is shown
712 function() {
713 page.open(url, function(status) {
714 // set the phrase
715 var expected = "xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG";
716 page.evaluate(function() {
717 $(".phrase").val("abandon abandon ability");
718 $(".phrase").trigger("input");
719 });
720 // check the BIP44 extended private key
721 waitForGenerate(function() {
722 var actual = page.evaluate(function() {
723 return $(".extended-priv-key").val();
724 });
725 if (actual != expected) {
726 console.log("BIP44 extended private key is incorrect");
727 console.log("Expected: " + expected);
728 console.log("Actual: " + actual);
729 fail();
730 }
731 next();
732 });
733 });
734 },
735
736 // BIP44 extended public key is shown
737 function() {
738 page.open(url, function(status) {
739 // set the phrase
740 var expected = "xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM";
741 page.evaluate(function() {
742 $(".phrase").val("abandon abandon ability");
743 $(".phrase").trigger("input");
744 });
745 // check the BIP44 extended public key
746 waitForGenerate(function() {
747 var actual = page.evaluate(function() {
748 return $(".extended-pub-key").val();
749 });
750 if (actual != expected) {
751 console.log("BIP44 extended public key is incorrect");
752 console.log("Expected: " + expected);
753 console.log("Actual: " + actual);
754 fail();
755 }
756 next();
757 });
758 });
759 },
760
761 // BIP44 purpose field changes address list
762 function() {
763 page.open(url, function(status) {
764 // set the phrase
765 var expected = "1JbDzRJ2cDT8aat2xwKd6Pb2zzavow5MhF";
766 page.evaluate(function() {
767 $(".phrase").val("abandon abandon ability");
768 $(".phrase").trigger("input");
769 });
770 waitForGenerate(function() {
771 // change the bip44 purpose field to 45
772 page.evaluate(function() {
773 $("#bip44 .purpose").val("45");
774 $("#bip44 .purpose").trigger("input");
775 });
776 waitForGenerate(function() {
777 // check the address for the new derivation path
778 var actual = page.evaluate(function() {
779 return $(".address:first").text();
780 });
781 if (actual != expected) {
782 console.log("BIP44 purpose field generates incorrect address");
783 console.log("Expected: " + expected);
784 console.log("Actual: " + actual);
785 fail();
786 }
787 next();
788 });
789 });
790 });
791 },
792
793 // BIP44 coin field changes address list
794 function() {
795 page.open(url, function(status) {
796 // set the phrase
797 var expected = "1F6dB2djQYrxoyfZZmfr6D5voH8GkJTghk";
798 page.evaluate(function() {
799 $(".phrase").val("abandon abandon ability");
800 $(".phrase").trigger("input");
801 });
802 waitForGenerate(function() {
803 // change the bip44 purpose field to 45
804 page.evaluate(function() {
805 $("#bip44 .coin").val("1");
806 $("#bip44 .coin").trigger("input");
807 });
808 waitForGenerate(function() {
809 // check the address for the new derivation path
810 var actual = page.evaluate(function() {
811 return $(".address:first").text();
812 });
813 if (actual != expected) {
814 console.log("BIP44 coin field generates incorrect address");
815 console.log("Expected: " + expected);
816 console.log("Actual: " + actual);
817 fail();
818 }
819 next();
820 });
821 });
822 });
823 },
824
825 // BIP44 account field changes address list
826 function() {
827 page.open(url, function(status) {
828 // set the phrase
829 var expected = "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
830 page.evaluate(function() {
831 $(".phrase").val("abandon abandon ability");
832 $(".phrase").trigger("input");
833 });
834 waitForGenerate(function() {
835 // change the bip44 purpose field to 45
836 page.evaluate(function() {
837 $("#bip44 .account").val("1");
838 $("#bip44 .account").trigger("input");
839 });
840 waitForGenerate(function() {
841 // check the address for the new derivation path
842 var actual = page.evaluate(function() {
843 return $(".address:first").text();
844 });
845 if (actual != expected) {
846 console.log("BIP44 account field generates incorrect address");
847 console.log("Expected: " + expected);
848 console.log("Actual: " + actual);
849 fail();
850 }
851 next();
852 });
853 });
854 });
855 },
856
857 // BIP44 change field changes address list
858 function() {
859 page.open(url, function(status) {
860 // set the phrase
861 var expected = "1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo";
862 page.evaluate(function() {
863 $(".phrase").val("abandon abandon ability");
864 $(".phrase").trigger("input");
865 });
866 waitForGenerate(function() {
867 // change the bip44 purpose field to 45
868 page.evaluate(function() {
869 $("#bip44 .change").val("1");
870 $("#bip44 .change").trigger("input");
871 });
872 waitForGenerate(function() {
873 // check the address for the new derivation path
874 var actual = page.evaluate(function() {
875 return $(".address:first").text();
876 });
877 if (actual != expected) {
878 console.log("BIP44 change field generates incorrect address");
879 console.log("Expected: " + expected);
880 console.log("Actual: " + actual);
881 fail();
882 }
883 next();
884 });
885 });
886 });
887 },
888
889 // BIP32 derivation path can be set
890 function() {
891 page.open(url, function(status) {
892 // set the phrase
893 var expected = "16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L";
894 page.evaluate(function() {
895 $(".phrase").val("abandon abandon ability");
896 $(".phrase").trigger("input");
897 });
898 // change tabs
899 waitForGenerate(function() {
900 page.evaluate(function() {
901 $("#bip32-tab a").click();
902 });
903 // set the derivation path to m/1
904 waitForGenerate(function() {
905 page.evaluate(function() {
906 $("#bip32 .path").val("m/1");
907 $("#bip32 .path").trigger("input");
908 });
909 // check the address is generated correctly
910 waitForGenerate(function() {
911 var actual = page.evaluate(function() {
912 return $(".address:first").text();
913 });
914 if (actual != expected) {
915 console.log("Custom BIP32 path generates incorrect address");
916 console.log("Expected: " + expected);
917 console.log("Actual: " + actual);
918 fail();
919 }
920 next();
921 });
922 });
923 });
924 });
925 },
926
927 // BIP32 can use hardened derivation paths
928 function() {
929 page.open(url, function(status) {
930 // set the phrase
931 var expected = "14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4";
932 page.evaluate(function() {
933 $(".phrase").val("abandon abandon ability");
934 $(".phrase").trigger("input");
935 });
936 // change tabs
937 waitForGenerate(function() {
938 page.evaluate(function() {
939 $("#bip32-tab a").click();
940 });
941 // set the derivation path to m/0'
942 waitForGenerate(function() {
943 page.evaluate(function() {
944 $("#bip32 .path").val("m/0'");
945 $("#bip32 .path").trigger("input");
946 });
947 // check the address is generated correctly
948 waitForGenerate(function() {
949 var actual = page.evaluate(function() {
950 return $(".address:first").text();
951 });
952 if (actual != expected) {
953 console.log("Hardened BIP32 path generates incorrect address");
954 console.log("Expected: " + expected);
955 console.log("Actual: " + actual);
956 fail();
957 }
958 next();
959 });
960 });
961 });
962 });
963 },
964
965 // BIP32 extended private key is shown
966 function() {
967 page.open(url, function(status) {
968 // set the phrase
969 var expected = "xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe";
970 page.evaluate(function() {
971 $(".phrase").val("abandon abandon ability");
972 $(".phrase").trigger("input");
973 });
974 // change tabs
975 waitForGenerate(function() {
976 page.evaluate(function() {
977 $("#bip32-tab a").click();
978 });
979 // check the extended private key is generated correctly
980 waitForGenerate(function() {
981 var actual = page.evaluate(function() {
982 return $(".extended-priv-key").val();
983 });
984 if (actual != expected) {
985 console.log("BIP32 extended private key is incorrect");
986 console.log("Expected: " + expected);
987 console.log("Actual: " + actual);
988 fail();
989 }
990 next();
991 });
992 });
993 });
994 },
995
996 // BIP32 extended public key is shown
997 function() {
998 page.open(url, function(status) {
999 // set the phrase
1000 var expected = "xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P";
1001 page.evaluate(function() {
1002 $(".phrase").val("abandon abandon ability");
1003 $(".phrase").trigger("input");
1004 });
1005 // change tabs
1006 waitForGenerate(function() {
1007 page.evaluate(function() {
1008 $("#bip32-tab a").click();
1009 });
1010 // check the extended public key is generated correctly
1011 waitForGenerate(function() {
1012 var actual = page.evaluate(function() {
1013 return $(".extended-pub-key").val();
1014 });
1015 if (actual != expected) {
1016 console.log("BIP32 extended public key is incorrect");
1017 console.log("Expected: " + expected);
1018 console.log("Actual: " + actual);
1019 fail();
1020 }
1021 next();
1022 });
1023 });
1024 });
1025 },
1026
1027 // Derivation path is shown in table
1028 function() {
1029 page.open(url, function(status) {
1030 // set the phrase
1031 var expected = "m/44'/0'/0'/0/0";
1032 page.evaluate(function() {
1033 $(".phrase").val("abandon abandon ability");
1034 $(".phrase").trigger("input");
1035 });
1036 // check for derivation path in table
1037 waitForGenerate(function() {
1038 var actual = page.evaluate(function() {
1039 return $(".index:first").text();
1040 });
1041 if (actual != expected) {
1042 console.log("Derivation path shown incorrectly in table");
1043 console.log("Expected: " + expected);
1044 console.log("Actual: " + actual);
1045 fail();
1046 }
1047 next();
1048 });
1049 });
1050 },
1051
1052 // Derivation path for address can be hardened
1053 function() {
1054 page.open(url, function(status) {
1055 // set the phrase
1056 var expected = "18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd";
1057 page.evaluate(function() {
1058 $(".phrase").val("abandon abandon ability");
1059 $(".phrase").trigger("input");
1060 });
1061 // change tabs
1062 waitForGenerate(function() {
1063 page.evaluate(function() {
1064 $("#bip32-tab a").click();
1065 });
1066 waitForGenerate(function() {
1067 // select the hardened addresses option
1068 page.evaluate(function() {
1069 $(".hardened-addresses").prop("checked", true);
1070 $(".hardened-addresses").trigger("change");
1071 });
1072 waitForGenerate(function() {
1073 // check the generated address is hardened
1074 var actual = page.evaluate(function() {
1075 return $(".address:first").text();
1076 });
1077 if (actual != expected) {
1078 console.log("Hardened address is incorrect");
1079 console.log("Expected: " + expected);
1080 console.log("Actual: " + actual);
1081 fail();
1082 }
1083 next();
1084 });
1085 });
1086 });
1087 });
1088 },
1089
1090 // Derivation path visibility can be toggled
1091 function() {
1092 page.open(url, function(status) {
1093 // set the phrase
1094 page.evaluate(function() {
1095 $(".phrase").val("abandon abandon ability");
1096 $(".phrase").trigger("input");
1097 });
1098 waitForGenerate(function() {
1099 // toggle path visibility
1100 page.evaluate(function() {
1101 $(".index-toggle").click();
1102 });
1103 // check the path is not visible
1104 var isInvisible = page.evaluate(function() {
1105 return $(".index:first span").hasClass("invisible");
1106 });
1107 if (!isInvisible) {
1108 console.log("Toggled derivation path is visible");
1109 fail();
1110 }
1111 next();
1112 });
1113 });
1114 },
1115
1116 // Address is shown
1117 function() {
1118 page.open(url, function(status) {
1119 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1120 // set the phrase
1121 page.evaluate(function() {
1122 $(".phrase").val("abandon abandon ability").trigger("input");
1123 });
1124 // get the address
1125 waitForGenerate(function() {
1126 var actual = page.evaluate(function() {
1127 return $(".address:first").text();
1128 });
1129 if (actual != expected) {
1130 console.log("Address is not shown");
1131 console.log("Expected: " + expected);
1132 console.log("Got: " + actual);
1133 fail();
1134 }
1135 next();
1136 });
1137 });
1138 },
1139
1140 // Addresses are shown in order of derivation path
1141 function() {
1142 page.open(url, function(status) {
1143 // set the phrase
1144 page.evaluate(function() {
1145 $(".phrase").val("abandon abandon ability").trigger("input");
1146 });
1147 // get the derivation paths
1148 waitForGenerate(function() {
1149 var paths = page.evaluate(function() {
1150 return $(".index").map(function(i, e) {
1151 return $(e).text();
1152 });
1153 });
1154 if (paths.length != 20) {
1155 console.log("Total paths is less than expected: " + paths.length);
1156 fail();
1157 }
1158 for (var i=0; i<paths.length; i++) {
1159 var expected = "m/44'/0'/0'/0/" + i;
1160 var actual = paths[i];
1161 if (actual != expected) {
1162 console.log("Path " + i + " is incorrect");
1163 console.log("Expected: " + expected);
1164 console.log("Actual: " + actual);
1165 fail();
1166 }
1167 }
1168 next();
1169 });
1170 });
1171 },
1172
1173 // Address visibility can be toggled
1174 function() {
1175 page.open(url, function(status) {
1176 // set the phrase
1177 page.evaluate(function() {
1178 $(".phrase").val("abandon abandon ability");
1179 $(".phrase").trigger("input");
1180 });
1181 waitForGenerate(function() {
1182 // toggle address visibility
1183 page.evaluate(function() {
1184 $(".address-toggle").click();
1185 });
1186 // check the address is not visible
1187 var isInvisible = page.evaluate(function() {
1188 return $(".address:first span").hasClass("invisible");
1189 });
1190 if (!isInvisible) {
1191 console.log("Toggled address is visible");
1192 fail();
1193 }
1194 next();
1195 });
1196 });
1197 },
1198
1199 // Public key is shown
1200 function() {
1201 page.open(url, function(status) {
1202 var expected = "033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3";
1203 // set the phrase
1204 page.evaluate(function() {
1205 $(".phrase").val("abandon abandon ability").trigger("input");
1206 });
1207 // get the address
1208 waitForGenerate(function() {
1209 var actual = page.evaluate(function() {
1210 return $(".pubkey:first").text();
1211 });
1212 if (actual != expected) {
1213 console.log("Public key is not shown");
1214 console.log("Expected: " + expected);
1215 console.log("Got: " + actual);
1216 fail();
1217 }
1218 next();
1219 });
1220 });
1221 },
1222
1223 // Public key visibility can be toggled
1224 function() {
1225 page.open(url, function(status) {
1226 // set the phrase
1227 page.evaluate(function() {
1228 $(".phrase").val("abandon abandon ability");
1229 $(".phrase").trigger("input");
1230 });
1231 waitForGenerate(function() {
1232 // toggle public key visibility
1233 page.evaluate(function() {
1234 $(".public-key-toggle").click();
1235 });
1236 // check the public key is not visible
1237 var isInvisible = page.evaluate(function() {
1238 return $(".pubkey:first span").hasClass("invisible");
1239 });
1240 if (!isInvisible) {
1241 console.log("Toggled public key is visible");
1242 fail();
1243 }
1244 next();
1245 });
1246 });
1247 },
1248
1249 // Private key is shown
1250 function() {
1251 page.open(url, function(status) {
1252 var expected = "L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE";
1253 // set the phrase
1254 page.evaluate(function() {
1255 $(".phrase").val("abandon abandon ability").trigger("input");
1256 });
1257 // get the address
1258 waitForGenerate(function() {
1259 var actual = page.evaluate(function() {
1260 return $(".privkey:first").text();
1261 });
1262 if (actual != expected) {
1263 console.log("Private key is not shown");
1264 console.log("Expected: " + expected);
1265 console.log("Got: " + actual);
1266 fail();
1267 }
1268 next();
1269 });
1270 });
1271 },
1272
1273 // Private key visibility can be toggled
1274 function() {
1275 page.open(url, function(status) {
1276 // set the phrase
1277 page.evaluate(function() {
1278 $(".phrase").val("abandon abandon ability");
1279 $(".phrase").trigger("input");
1280 });
1281 waitForGenerate(function() {
1282 // toggle private key visibility
1283 page.evaluate(function() {
1284 $(".private-key-toggle").click();
1285 });
1286 // check the private key is not visible
1287 var isInvisible = page.evaluate(function() {
1288 return $(".privkey:first span").hasClass("invisible");
1289 });
1290 if (!isInvisible) {
1291 console.log("Toggled private key is visible");
1292 fail();
1293 }
1294 next();
1295 });
1296 });
1297 },
1298
1299 // More addresses can be generated
1300 function() {
1301 page.open(url, function(status) {
1302 // set the phrase
1303 page.evaluate(function() {
1304 $(".phrase").val("abandon abandon ability");
1305 $(".phrase").trigger("input");
1306 });
1307 waitForGenerate(function() {
1308 // generate more addresses
1309 page.evaluate(function() {
1310 $(".more").click();
1311 });
1312 waitForGenerate(function() {
1313 // check there are more addresses
1314 var addressCount = page.evaluate(function() {
1315 return $(".address").length;
1316 });
1317 if (addressCount != 40) {
1318 console.log("More addresses cannot be generated");
1319 fail();
1320 }
1321 next();
1322 });
1323 });
1324 });
1325 },
1326
1327 // A custom number of additional addresses can be generated
1328 function() {
1329 page.open(url, function(status) {
1330 // set the phrase
1331 page.evaluate(function() {
1332 $(".phrase").val("abandon abandon ability");
1333 $(".phrase").trigger("input");
1334 });
1335 waitForGenerate(function() {
1336 // get the current number of addresses
1337 var oldAddressCount = page.evaluate(function() {
1338 return $(".address").length;
1339 });
1340 // set a custom number of additional addresses
1341 page.evaluate(function() {
1342 $(".rows-to-add").val(1);
1343 });
1344 // generate more addresses
1345 page.evaluate(function() {
1346 $(".more").click();
1347 });
1348 waitForGenerate(function() {
1349 // check there are the correct number of addresses
1350 var newAddressCount = page.evaluate(function() {
1351 return $(".address").length;
1352 });
1353 if (newAddressCount - oldAddressCount != 1) {
1354 console.log("Number of additional addresses cannot be customized");
1355 console.log(newAddressCount)
1356 console.log(oldAddressCount)
1357 fail();
1358 }
1359 next();
1360 });
1361 });
1362 });
1363 },
1364
1365 // Additional addresses are shown in order of derivation path
1366 function() {
1367 page.open(url, function(status) {
1368 // set the phrase
1369 page.evaluate(function() {
1370 $(".phrase").val("abandon abandon ability").trigger("input");
1371 });
1372 waitForGenerate(function() {
1373 // generate more addresses
1374 page.evaluate(function() {
1375 $(".more").click();
1376 });
1377 // get the derivation paths
1378 waitForGenerate(function() {
1379 var paths = page.evaluate(function() {
1380 return $(".index").map(function(i, e) {
1381 return $(e).text();
1382 });
1383 });
1384 if (paths.length != 40) {
1385 console.log("Total additional paths is less than expected: " + paths.length);
1386 fail();
1387 }
1388 for (var i=0; i<paths.length; i++) {
1389 var expected = "m/44'/0'/0'/0/" + i;
1390 var actual = paths[i];
1391 if (actual != expected) {
1392 console.log("Path " + i + " is not in correct order");
1393 console.log("Expected: " + expected);
1394 console.log("Actual: " + actual);
1395 fail();
1396 }
1397 }
1398 next();
1399 });
1400 });
1401 });
1402 },
1403
1404 // BIP32 root key can be set by the user
1405 function() {
1406 page.open(url, function(status) {
1407 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1408 // set the root key
1409 page.evaluate(function() {
1410 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1411 });
1412 waitForGenerate(function() {
1413 var actual = page.evaluate(function() {
1414 return $(".address:first").text();
1415 });
1416 if (actual != expected) {
1417 console.log("Setting BIP32 root key results in wrong address");
1418 console.log("Expected: " + expected);
1419 console.log("Actual: " + actual);
1420 fail();
1421 }
1422 next();
1423 });
1424 });
1425 },
1426
1427 // Setting BIP32 root key clears the existing phrase, passphrase and seed
1428 function() {
1429 page.open(url, function(status) {
1430 var expected = "";
1431 // set a mnemonic
1432 page.evaluate(function() {
1433 $(".phrase").val("A non-blank but invalid value");
1434 });
1435 // Accept any confirm dialogs
1436 page.onConfirm = function() {
1437 return true;
1438 };
1439 // set the root key
1440 page.evaluate(function() {
1441 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1442 });
1443 waitForGenerate(function() {
1444 var actual = page.evaluate(function() {
1445 return $(".phrase").val();
1446 });
1447 if (actual != expected) {
1448 console.log("Phrase not cleared when setting BIP32 root key");
1449 console.log("Expected: " + expected);
1450 console.log("Actual: " + actual);
1451 fail();
1452 }
1453 next();
1454 });
1455 });
1456 },
1457
1458 // Clearing of phrase, passphrase and seed can be cancelled by user
1459 function() {
1460 page.open(url, function(status) {
1461 var expected = "abandon abandon ability";
1462 // set a mnemonic
1463 page.evaluate(function() {
1464 $(".phrase").val("abandon abandon ability");
1465 });
1466 // Cancel any confirm dialogs
1467 page.onConfirm = function() {
1468 return false;
1469 };
1470 // set the root key
1471 page.evaluate(function() {
1472 $(".root-key").val("xprv9s21ZrQH143K3d3vzEDD3KpSKmxsZ3y7CqhAL1tinwtP6wqK4TKEKjpBuo6P2hUhB6ZENo7TTSRytiP857hBZVpBdk8PooFuRspE1eywwNZ").trigger("input");
1473 });
1474 var actual = page.evaluate(function() {
1475 return $(".phrase").val();
1476 });
1477 if (actual != expected) {
1478 console.log("Phrase not retained when cancelling changes to BIP32 root key");
1479 console.log("Expected: " + expected);
1480 console.log("Actual: " + actual);
1481 fail();
1482 }
1483 next();
1484 });
1485 },
1486
1487 // Custom BIP32 root key is used when changing the derivation path
1488 function() {
1489 page.open(url, function(status) {
1490 var expected = "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
1491 // set the root key
1492 page.evaluate(function() {
1493 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1494 });
1495 waitForGenerate(function() {
1496 // change the derivation path
1497 page.evaluate(function() {
1498 $("#account").val("1").trigger("input");
1499 });
1500 // check the bip32 root key is used for derivation, not the blank phrase
1501 waitForGenerate(function() {
1502 var actual = page.evaluate(function() {
1503 return $(".address:first").text();
1504 });
1505 if (actual != expected) {
1506 console.log("Changing the derivation path does not use BIP32 root key");
1507 console.log("Expected: " + expected);
1508 console.log("Actual: " + actual);
1509 fail();
1510 }
1511 next();
1512 });
1513 });
1514 });
1515 },
1516
1517 // Incorrect mnemonic shows error
1518 function() {
1519 page.open(url, function(status) {
1520 // set the root key
1521 page.evaluate(function() {
1522 $(".phrase").val("abandon abandon abandon").trigger("input");
1523 });
1524 waitForFeedback(function() {
1525 // check there is an error shown
1526 var feedback = page.evaluate(function() {
1527 return $(".feedback").text();
1528 });
1529 if (feedback.length <= 0) {
1530 console.log("Invalid mnemonic does not show error");
1531 fail();
1532 }
1533 next();
1534 });
1535 });
1536 },
1537
1538 // Incorrect word shows suggested replacement
1539 function() {
1540 page.open(url, function(status) {
1541 // set the root key
1542 page.evaluate(function() {
1543 $(".phrase").val("abandon abandon abiliti").trigger("input");
1544 });
1545 // check there is a suggestion shown
1546 waitForFeedback(function() {
1547 var feedback = page.evaluate(function() {
1548 return $(".feedback").text();
1549 });
1550 if (feedback.indexOf("did you mean ability?") < 0) {
1551 console.log("Incorrect word does not show suggested replacement");
1552 console.log("Error: " + error);
1553 fail();
1554 }
1555 next();
1556 });
1557 });
1558 },
1559
1560 // Incorrect BIP32 root key shows error
1561 function() {
1562 page.open(url, function(status) {
1563 // set the root key
1564 page.evaluate(function() {
1565 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj").trigger("input");
1566 });
1567 // check there is an error shown
1568 waitForFeedback(function() {
1569 var feedback = page.evaluate(function() {
1570 return $(".feedback").text();
1571 });
1572 if (feedback != "Invalid root key") {
1573 console.log("Invalid root key does not show error");
1574 console.log("Error: " + error);
1575 fail();
1576 }
1577 next();
1578 });
1579 });
1580 },
1581
1582 // Derivation path not starting with m shows error
1583 function() {
1584 page.open(url, function(status) {
1585 // set the mnemonic phrase
1586 page.evaluate(function() {
1587 $(".phrase").val("abandon abandon ability").trigger("input");
1588 });
1589 waitForGenerate(function() {
1590 // select the bip32 tab so custom derivation path can be set
1591 page.evaluate(function() {
1592 $("#bip32-tab a").click();
1593 });
1594 waitForGenerate(function() {
1595 // set the incorrect derivation path
1596 page.evaluate(function() {
1597 $("#bip32 .path").val("n/0").trigger("input");
1598 });
1599 waitForFeedback(function() {
1600 var feedback = page.evaluate(function() {
1601 return $(".feedback").text();
1602 });
1603 if (feedback != "First character must be 'm'") {
1604 console.log("Derivation path not starting with m should show error");
1605 console.log("Error: " + error);
1606 fail();
1607 }
1608 next();
1609 });
1610 });
1611 });
1612 });
1613 },
1614
1615 // Derivation path containing invalid characters shows useful error
1616 function() {
1617 page.open(url, function(status) {
1618 // set the mnemonic phrase
1619 page.evaluate(function() {
1620 $(".phrase").val("abandon abandon ability").trigger("input");
1621 });
1622 waitForGenerate(function() {
1623 // select the bip32 tab so custom derivation path can be set
1624 page.evaluate(function() {
1625 $("#bip32-tab a").click();
1626 });
1627 waitForGenerate(function() {
1628 // set the incorrect derivation path
1629 page.evaluate(function() {
1630 $("#bip32 .path").val("m/1/0wrong1/1").trigger("input");
1631 });
1632 waitForFeedback(function() {
1633 var feedback = page.evaluate(function() {
1634 return $(".feedback").text();
1635 });
1636 if (feedback != "Invalid characters 0wrong1 found at depth 2") {
1637 console.log("Derivation path with invalid characters should show error");
1638 console.log("Error: " + error);
1639 fail();
1640 }
1641 next();
1642 });
1643 });
1644 });
1645 });
1646 },
1647
1648 // Github Issue 11: Default word length is 15
1649 // https://github.com/iancoleman/bip39/issues/11
1650 function() {
1651 page.open(url, function(status) {
1652 // get the word length
1653 var defaultLength = page.evaluate(function() {
1654 return $(".strength").val();
1655 });
1656 if (defaultLength != 15) {
1657 console.log("Default word length is not 15");
1658 fail();
1659 }
1660 next();
1661 });
1662 },
1663
1664
1665 // Github Issue 12: Generate more rows with private keys hidden
1666 // https://github.com/iancoleman/bip39/issues/12
1667 function() {
1668 page.open(url, function(status) {
1669 // set the phrase
1670 page.evaluate(function() {
1671 $(".phrase").val("abandon abandon ability");
1672 $(".phrase").trigger("input");
1673 });
1674 waitForGenerate(function() {
1675 // toggle private keys hidden, then generate more addresses
1676 page.evaluate(function() {
1677 $(".private-key-toggle").click();
1678 $(".more").click();
1679 });
1680 waitForGenerate(function() {
1681 // check more have been generated
1682 var expected = 40;
1683 var numPrivKeys = page.evaluate(function() {
1684 return $(".privkey").length;
1685 });
1686 if (numPrivKeys != expected) {
1687 console.log("Wrong number of addresses when clicking 'more' with hidden privkeys");
1688 console.log("Expected: " + expected);
1689 console.log("Actual: " + numPrivKeys);
1690 fail();
1691 }
1692 // check no private keys are shown
1693 var numHiddenPrivKeys = page.evaluate(function() {
1694 return $(".privkey span[class=invisible]").length;
1695 });
1696 if (numHiddenPrivKeys != expected) {
1697 console.log("Generating more does not retain hidden state of privkeys");
1698 console.log("Expected: " + expected);
1699 console.log("Actual: " + numHiddenPrivKeys);
1700 fail();
1701 }
1702 next();
1703 });
1704 });
1705 });
1706 },
1707
1708 // Github Issue 19: Mnemonic is not sensitive to whitespace
1709 // https://github.com/iancoleman/bip39/issues/19
1710 function() {
1711 page.open(url, function(status) {
1712 // set the phrase
1713 var expected = "xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC";
1714 page.evaluate(function() {
1715 var doubleSpace = " ";
1716 $(".phrase").val("urge cat" + doubleSpace + "bid");
1717 $(".phrase").trigger("input");
1718 });
1719 waitForGenerate(function() {
1720 // Check the bip32 root key is correct
1721 var actual = page.evaluate(function() {
1722 return $(".root-key").val();
1723 });
1724 if (actual != expected) {
1725 console.log("Mnemonic is sensitive to whitespace");
1726 console.log("Expected: " + expected);
1727 console.log("Actual: " + actual);
1728 fail();
1729 }
1730 next();
1731 });
1732 });
1733 },
1734
1735 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1736 // https://github.com/iancoleman/bip39/issues/23
1737 function() {
1738 page.open(url, function(status) {
1739 // 1) and 2) set the phrase
1740 page.evaluate(function() {
1741 $(".phrase").val("abandon abandon ability").trigger("input");
1742 });
1743 waitForGenerate(function() {
1744 // 3) select bip32 tab
1745 page.evaluate(function() {
1746 $("#bip32-tab a").click();
1747 });
1748 waitForGenerate(function() {
1749 // 4) switch from bitcoin to litecoin
1750 page.evaluate(function() {
1751 $(".network").val("2").trigger("change");
1752 });
1753 waitForGenerate(function() {
1754 // 5) Check derivation path is displayed correctly
1755 var expected = "m/0/0";
1756 var actual = page.evaluate(function() {
1757 return $(".index:first").text();
1758 });
1759 if (actual != expected) {
1760 console.log("Github Issue 23 Part 1: derivation path display error");
1761 console.log("Expected: " + expected);
1762 console.log("Actual: " + actual);
1763 fail();
1764 }
1765 // 5) Check address is displayed correctly
1766 var expected = "LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5";
1767 var actual = page.evaluate(function() {
1768 return $(".address:first").text();
1769 });
1770 if (actual != expected) {
1771 console.log("Github Issue 23 Part 1: address display error");
1772 console.log("Expected: " + expected);
1773 console.log("Actual: " + actual);
1774 fail();
1775 }
1776 next();
1777 });
1778 });
1779 });
1780 });
1781 },
1782
1783 // Github Issue 23 Part 2: Coin selection in derivation path
1784 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1785 function() {
1786 page.open(url, function(status) {
1787 // set the phrase
1788 page.evaluate(function() {
1789 $(".phrase").val("abandon abandon ability").trigger("input");
1790 });
1791 waitForGenerate(function() {
1792 // switch from bitcoin to clam
1793 page.evaluate(function() {
1794 $(".network").val("9").trigger("change");
1795 });
1796 waitForGenerate(function() {
1797 // check derivation path is displayed correctly
1798 var expected = "m/44'/23'/0'/0/0";
1799 var actual = page.evaluate(function() {
1800 return $(".index:first").text();
1801 });
1802 if (actual != expected) {
1803 console.log("Github Issue 23 Part 2: Coin in BIP44 derivation path is incorrect");
1804 console.log("Expected: " + expected);
1805 console.log("Actual: " + actual);
1806 fail();
1807 }
1808 next();
1809 });
1810 });
1811 });
1812 },
1813
1814 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1815 // https://github.com/iancoleman/bip39/issues/26
1816 function() {
1817 page.open(url, function(status) {
1818 // 1) 2) and 3) set the root key
1819 page.evaluate(function() {
1820 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1821 });
1822 waitForGenerate(function() {
1823 // 4) switch from bitcoin to viacoin
1824 page.evaluate(function() {
1825 $(".network").val("6").trigger("change");
1826 });
1827 waitForGenerate(function() {
1828 // 5) ensure the derived address is correct
1829 var expected = "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
1830 var actual = page.evaluate(function() {
1831 return $(".address:first").text();
1832 });
1833 if (actual != expected) {
1834 console.log("Github Issue 26: address is incorrect when changing networks and using root-key to derive");
1835 console.log("Expected: " + expected);
1836 console.log("Actual: " + actual);
1837 fail();
1838 }
1839 next();
1840 });
1841 });
1842 });
1843 },
1844
1845 // Selecting a language with no existing phrase should generate a phrase in
1846 // that language.
1847 function() {
1848 page.open(url, function(status) {
1849 // Select a language
1850 // Need to manually simulate hash being set due to quirk between
1851 // 'click' event triggered by javascript vs triggered by mouse.
1852 // Perhaps look into page.sendEvent
1853 // http://phantomjs.org/api/webpage/method/send-event.html
1854 page.evaluate(function() {
1855 window.location.hash = "#japanese";
1856 $("a[href='#japanese']").trigger("click");
1857 });
1858 waitForGenerate(function() {
1859 // Check the mnemonic is in Japanese
1860 var phrase = page.evaluate(function() {
1861 return $(".phrase").val();
1862 });
1863 if (phrase.length <= 0) {
1864 console.log("No Japanese phrase generated");
1865 fail();
1866 }
1867 if (phrase.charCodeAt(0) < 128) {
1868 console.log("First character of Japanese phrase is ascii");
1869 console.log("Phrase: " + phrase);
1870 fail();
1871 }
1872 next();
1873 });
1874 });
1875 },
1876
1877 // Selecting a language with existing phrase should update the phrase to use
1878 // that language.
1879 function() {
1880 page.open(url, function(status) {
1881 // Set the phrase to an English phrase.
1882 page.evaluate(function() {
1883 $(".phrase").val("abandon abandon ability").trigger("input");
1884 });
1885 waitForGenerate(function() {
1886 // Change to Italian
1887 // Need to manually simulate hash being set due to quirk between
1888 // 'click' event triggered by javascript vs triggered by mouse.
1889 // Perhaps look into page.sendEvent
1890 // http://phantomjs.org/api/webpage/method/send-event.html
1891 page.evaluate(function() {
1892 window.location.hash = "#italian";
1893 $("a[href='#italian']").trigger("click");
1894 });
1895 waitForGenerate(function() {
1896 // Check only the language changes, not the phrase
1897 var expected = "abaco abaco abbaglio";
1898 var actual = page.evaluate(function() {
1899 return $(".phrase").val();
1900 });
1901 if (actual != expected) {
1902 console.log("Changing language with existing phrase");
1903 console.log("Expected: " + expected);
1904 console.log("Actual: " + actual);
1905 fail();
1906 }
1907 // Check the address is correct
1908 var expected = "1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV";
1909 var actual = page.evaluate(function() {
1910 return $(".address:first").text();
1911 });
1912 if (actual != expected) {
1913 console.log("Changing language generates incorrect address");
1914 console.log("Expected: " + expected);
1915 console.log("Actual: " + actual);
1916 fail();
1917 }
1918 next();
1919 });
1920 });
1921 });
1922 },
1923
1924 // Suggested replacement for erroneous word in non-English language
1925 function() {
1926 page.open(url, function(status) {
1927 // Set an incorrect phrase in Italian
1928 page.evaluate(function() {
1929 $(".phrase").val("abaco abaco zbbaglio").trigger("input");
1930 });
1931 waitForFeedback(function() {
1932 // Check the suggestion is correct
1933 var feedback = page.evaluate(function() {
1934 return $(".feedback").text();
1935 });
1936 if (feedback.indexOf("did you mean abbaglio?") < 0) {
1937 console.log("Incorrect Italian word does not show suggested replacement");
1938 console.log("Error: " + error);
1939 fail();
1940 }
1941 next();
1942 });
1943 });
1944 },
1945
1946
1947 // Japanese word does not break across lines.
1948 // Point 2 from
1949 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
1950 function() {
1951 page.open(url, function(status) {
1952 hasWordBreakCss = page.content.indexOf("word-break: keep-all;") > -1;
1953 if (!hasWordBreakCss) {
1954 console.log("Japanese words can break across lines mid-word");
1955 console.log("Check CSS for '.phrase { word-break: keep-all; }'");
1956 fail();
1957 }
1958 // Run the next test
1959 next();
1960 });
1961 },
1962
1963 // Language can be specified at page load using hash value in url
1964 function() {
1965 page.open(url, function(status) {
1966 // Set the page hash as if it were on a fresh page load
1967 page.evaluate(function() {
1968 window.location.hash = "#japanese";
1969 });
1970 // Generate a random phrase
1971 page.evaluate(function() {
1972 $(".generate").trigger("click");
1973 });
1974 waitForGenerate(function() {
1975 // Check the phrase is in Japanese
1976 var phrase = page.evaluate(function() {
1977 return $(".phrase").val();
1978 });
1979 if (phrase.length <= 0) {
1980 console.log("No phrase generated using url hash");
1981 fail();
1982 }
1983 if (phrase.charCodeAt(0) < 128) {
1984 console.log("Language not detected from url hash on page load.");
1985 console.log("Phrase: " + phrase);
1986 fail();
1987 }
1988 next();
1989 });
1990 });
1991 },
1992
1993 // Entropy unit tests
1994 function() {
1995 page.open(url, function(status) {
1996 var response = page.evaluate(function() {
1997 var e;
1998 // binary entropy is detected
1999 try {
2000 e = Entropy.fromString("01010101");
2001 if (e.base.str != "binary") {
2002 return "Binary entropy not detected correctly";
2003 }
2004 }
2005 catch (e) {
2006 return e.message;
2007 }
2008 // base6 entropy is detected
2009 try {
2010 e = Entropy.fromString("012345012345");
2011 if (e.base.str != "base 6") {
2012 return "base6 entropy not detected correctly";
2013 }
2014 }
2015 catch (e) {
2016 return e.message;
2017 }
2018 // dice entropy is detected
2019 try {
2020 e = Entropy.fromString("123456123456");
2021 if (e.base.str != "base 6 (dice)") {
2022 return "dice entropy not detected correctly";
2023 }
2024 }
2025 catch (e) {
2026 return e.message;
2027 }
2028 // base10 entropy is detected
2029 try {
2030 e = Entropy.fromString("0123456789");
2031 if (e.base.str != "base 10") {
2032 return "base10 entropy not detected correctly";
2033 }
2034 }
2035 catch (e) {
2036 return e.message;
2037 }
2038 // hex entropy is detected
2039 try {
2040 e = Entropy.fromString("0123456789ABCDEF");
2041 if (e.base.str != "hexadecimal") {
2042 return "hexadecimal entropy not detected correctly";
2043 }
2044 }
2045 catch (e) {
2046 return e.message;
2047 }
2048 // card entropy is detected
2049 try {
2050 e = Entropy.fromString("AC4DTHKS");
2051 if (e.base.str != "card") {
2052 return "card entropy not detected correctly";
2053 }
2054 }
2055 catch (e) {
2056 return e.message;
2057 }
2058 // entropy is case insensitive
2059 try {
2060 e = Entropy.fromString("aBcDeF");
2061 if (e.cleanStr != "aBcDeF") {
2062 return "Entropy should not be case sensitive";
2063 }
2064 }
2065 catch (e) {
2066 return e.message;
2067 }
2068 // dice entropy is converted to base6
2069 try {
2070 e = Entropy.fromString("123456");
2071 if (e.cleanStr != "123450") {
2072 return "Dice entropy is not automatically converted to base6";
2073 }
2074 }
2075 catch (e) {
2076 return e.message;
2077 }
2078 // dice entropy is preferred to base6 if ambiguous
2079 try {
2080 e = Entropy.fromString("12345");
2081 if (e.base.str != "base 6 (dice)") {
2082 return "dice not used as default over base 6";
2083 }
2084 }
2085 catch (e) {
2086 return e.message;
2087 }
2088 // unused characters are ignored
2089 try {
2090 e = Entropy.fromString("fghijkl");
2091 if (e.cleanStr != "f") {
2092 return "additional characters are not ignored";
2093 }
2094 }
2095 catch (e) {
2096 return e.message;
2097 }
2098 // the lowest base is used by default
2099 // 7 could be decimal or hexadecimal, but should be detected as decimal
2100 try {
2101 e = Entropy.fromString("7");
2102 if (e.base.str != "base 10") {
2103 return "lowest base is not used";
2104 }
2105 }
2106 catch (e) {
2107 return e.message;
2108 }
2109 // Leading zeros are retained
2110 try {
2111 e = Entropy.fromString("000A");
2112 if (e.cleanStr != "000A") {
2113 return "Leading zeros are not retained";
2114 }
2115 }
2116 catch (e) {
2117 return e.message;
2118 }
2119 // Leading zeros are correctly preserved for hex in binary string
2120 try {
2121 e = Entropy.fromString("2A");
2122 if (e.binaryStr != "00101010") {
2123 return "Hex leading zeros are not correct in binary";
2124 }
2125 }
2126 catch (e) {
2127 return e.message;
2128 }
2129 // Leading zeros for base 6 as binary string
2130 // 20 = 2 events at 2.58 bits per event = 5 bits
2131 // 20 in base 6 = 12 in base 10 = 1100 in base 2
2132 // so it needs 1 bit of padding to be the right bit length
2133 try {
2134 e = Entropy.fromString("20");
2135 if (e.binaryStr != "01100") {
2136 return "Base 6 as binary has leading zeros";
2137 }
2138 }
2139 catch (e) {
2140 return e.message;
2141 }
2142 // Leading zeros for base 10 as binary string
2143 try {
2144 e = Entropy.fromString("17");
2145 if (e.binaryStr != "010001") {
2146 return "Base 10 as binary has leading zeros";
2147 }
2148 }
2149 catch (e) {
2150 return e.message;
2151 }
2152 // Leading zeros for card entropy as binary string.
2153 // Card entropy is hashed so 2c does not produce leading zeros.
2154 try {
2155 e = Entropy.fromString("4c");
2156 if (e.binaryStr != "0001") {
2157 return "Card entropy as binary has leading zeros";
2158 }
2159 }
2160 catch (e) {
2161 return e.message;
2162 }
2163 // Keyboard mashing results in weak entropy
2164 // Despite being a long string, it's less than 30 bits of entropy
2165 try {
2166 e = Entropy.fromString("aj;se ifj; ask,dfv js;ifj");
2167 if (e.binaryStr.length >= 30) {
2168 return "Keyboard mashing should produce weak entropy";
2169 }
2170 }
2171 catch (e) {
2172 return e.message;
2173 }
2174 // Card entropy is used if every pair could be a card
2175 try {
2176 e = Entropy.fromString("4c3c2c");
2177 if (e.base.str != "card") {
2178 return "Card entropy not used if all pairs are cards";
2179 }
2180 }
2181 catch (e) {
2182 return e.message;
2183 }
2184 // Card entropy uses base 52
2185 // [ cards, binary ]
2186 try {
2187 var cards = [
2188 [ "ac", "0100" ],
2189 [ "acqs", "10111101" ],
2190 [ "acks", "11110000" ],
2191 [ "2cac", "11000010" ],
2192 [ "2c", "1000" ],
2193 [ "3d", "1111" ],
2194 [ "4h", "0011" ],
2195 [ "5s", "1001" ],
2196 [ "6c", "1011" ],
2197 [ "7d", "1101" ],
2198 [ "8h", "1011" ],
2199 [ "9s", "1010" ],
2200 [ "tc", "1101" ],
2201 [ "jd", "1101" ],
2202 [ "qh", "1100" ],
2203 [ "ks", "1111" ],
2204 [ "ks2c", "10000001" ],
2205 [ "KS2C", "10000001" ],
2206 ];
2207 for (var i=0; i<cards.length; i++) {
2208 var card = cards[i][0];
2209 var result = cards[i][1];
2210 e = Entropy.fromString(card);
2211 console.log(e.binary + " " + result);
2212 if (e.binaryStr !== result) {
2213 return "card entropy " + card + " not parsed correctly: " + result + " != " + e.binaryStr;
2214 }
2215 }
2216 }
2217 catch (e) {
2218 return e.message;
2219 }
2220 return "PASS";
2221 });
2222 if (response != "PASS") {
2223 console.log("Entropy unit tests");
2224 console.log(response);
2225 fail();
2226 };
2227 next();
2228 });
2229 },
2230
2231 // Entropy can be entered by the user
2232 function() {
2233 page.open(url, function(status) {
2234 expected = {
2235 mnemonic: "abandon abandon ability",
2236 address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug",
2237 }
2238 // use entropy
2239 page.evaluate(function() {
2240 $(".use-entropy").prop("checked", true).trigger("change");
2241 $(".mnemonic-length").val("raw");
2242 $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input");
2243 });
2244 // check the mnemonic is set and address is correct
2245 waitForGenerate(function() {
2246 var actual = page.evaluate(function() {
2247 return {
2248 address: $(".address:first").text(),
2249 mnemonic: $(".phrase").val(),
2250 }
2251 });
2252 if (actual.mnemonic != expected.mnemonic) {
2253 console.log("Entropy does not generate correct mnemonic");
2254 console.log("Expected: " + expected.mnemonic);
2255 console.log("Got: " + actual.mnemonic);
2256 fail();
2257 }
2258 if (actual.address != expected.address) {
2259 console.log("Entropy does not generate correct address");
2260 console.log("Expected: " + expected.address);
2261 console.log("Got: " + actual.address);
2262 fail();
2263 }
2264 next();
2265 });
2266 });
2267 },
2268
2269 // A warning about entropy is shown to the user, with additional information
2270 function() {
2271 page.open(url, function(status) {
2272 // get text content from entropy sections of page
2273 var hasWarning = page.evaluate(function() {
2274 var entropyText = $(".entropy-container").text();
2275 var warning = "mnemonic may be insecure";
2276 if (entropyText.indexOf(warning) == -1) {
2277 return false;
2278 }
2279 var readMoreText = $("#entropy-notes").parent().text();
2280 var goodSources = "flipping a fair coin, rolling a fair dice, noise measurements etc";
2281 if (readMoreText.indexOf(goodSources) == -1) {
2282 return false;
2283 }
2284 return true;
2285 });
2286 // check the warnings and information are shown
2287 if (!hasWarning) {
2288 console.log("Page does not contain warning about using own entropy");
2289 fail();
2290 }
2291 next();
2292 });
2293 },
2294
2295 // The types of entropy available are described to the user
2296 function() {
2297 page.open(url, function(status) {
2298 // get placeholder text for entropy field
2299 var placeholder = page.evaluate(function() {
2300 return $(".entropy").attr("placeholder");
2301 });
2302 var options = [
2303 "binary",
2304 "base 6",
2305 "dice",
2306 "base 10",
2307 "hexadecimal",
2308 "cards",
2309 ];
2310 for (var i=0; i<options.length; i++) {
2311 var option = options[i];
2312 if (placeholder.indexOf(option) == -1) {
2313 console.log("Available entropy type is not shown to user: " + option);
2314 fail();
2315 }
2316 }
2317 next();
2318 });
2319 },
2320
2321 // The actual entropy used is shown to the user
2322 function() {
2323 page.open(url, function(status) {
2324 // use entropy
2325 var badEntropySource = page.evaluate(function() {
2326 var entropy = "Not A Very Good Entropy Source At All";
2327 $(".use-entropy").prop("checked", true).trigger("change");
2328 $(".entropy").val(entropy).trigger("input");
2329 });
2330 // check the actual entropy being used is shown
2331 waitForEntropyFeedback(function() {
2332 var expectedText = "AedEceAA";
2333 var entropyText = page.evaluate(function() {
2334 return $(".entropy-container").text();
2335 });
2336 if (entropyText.indexOf(expectedText) == -1) {
2337 console.log("Actual entropy used is not shown");
2338 fail();
2339 }
2340 next();
2341 });
2342 });
2343 },
2344
2345 // Binary entropy can be entered
2346 function() {
2347 page.open(url, function(status) {
2348 // use entropy
2349 page.evaluate(function() {
2350 $(".use-entropy").prop("checked", true).trigger("change");
2351 $(".entropy").val("01").trigger("input");
2352 });
2353 // check the entropy is shown to be the correct type
2354 waitForEntropyFeedback(function() {
2355 var entropyText = page.evaluate(function() {
2356 return $(".entropy-container").text();
2357 });
2358 if (entropyText.indexOf("binary") == -1) {
2359 console.log("Binary entropy is not detected and presented to user");
2360 fail();
2361 }
2362 next();
2363 });
2364 });
2365 },
2366
2367 // Base 6 entropy can be entered
2368 function() {
2369 page.open(url, function(status) {
2370 // use entropy
2371 page.evaluate(function() {
2372 $(".use-entropy").prop("checked", true).trigger("change");
2373 $(".entropy").val("012345").trigger("input");
2374 });
2375 // check the entropy is shown to be the correct type
2376 waitForEntropyFeedback(function() {
2377 var entropyText = page.evaluate(function() {
2378 return $(".entropy-container").text();
2379 });
2380 if (entropyText.indexOf("base 6") == -1) {
2381 console.log("Base 6 entropy is not detected and presented to user");
2382 fail();
2383 }
2384 next();
2385 });
2386 });
2387 },
2388
2389 // Base 6 dice entropy can be entered
2390 function() {
2391 page.open(url, function(status) {
2392 // use entropy
2393 page.evaluate(function() {
2394 $(".use-entropy").prop("checked", true).trigger("change");
2395 $(".entropy").val("123456").trigger("input");
2396 });
2397 // check the entropy is shown to be the correct type
2398 waitForEntropyFeedback(function() {
2399 var entropyText = page.evaluate(function() {
2400 return $(".entropy-container").text();
2401 });
2402 if (entropyText.indexOf("dice") == -1) {
2403 console.log("Dice entropy is not detected and presented to user");
2404 fail();
2405 }
2406 next();
2407 });
2408 });
2409 },
2410
2411 // Base 10 entropy can be entered
2412 function() {
2413 page.open(url, function(status) {
2414 // use entropy
2415 page.evaluate(function() {
2416 $(".use-entropy").prop("checked", true).trigger("change");
2417 $(".entropy").val("789").trigger("input");
2418 });
2419 // check the entropy is shown to be the correct type
2420 waitForEntropyFeedback(function() {
2421 var entropyText = page.evaluate(function() {
2422 return $(".entropy-container").text();
2423 });
2424 if (entropyText.indexOf("base 10") == -1) {
2425 console.log("Base 10 entropy is not detected and presented to user");
2426 fail();
2427 }
2428 next();
2429 });
2430 });
2431 },
2432
2433 // Hexadecimal entropy can be entered
2434 function() {
2435 page.open(url, function(status) {
2436 // use entropy
2437 page.evaluate(function() {
2438 $(".use-entropy").prop("checked", true).trigger("change");
2439 $(".entropy").val("abcdef").trigger("input");
2440 });
2441 // check the entropy is shown to be the correct type
2442 waitForEntropyFeedback(function() {
2443 var entropyText = page.evaluate(function() {
2444 return $(".entropy-container").text();
2445 });
2446 if (entropyText.indexOf("hexadecimal") == -1) {
2447 console.log("Hexadecimal entropy is not detected and presented to user");
2448 fail();
2449 }
2450 next();
2451 });
2452 });
2453 },
2454
2455 // Dice entropy value is shown as the converted base 6 value
2456 function() {
2457 page.open(url, function(status) {
2458 // use entropy
2459 page.evaluate(function() {
2460 $(".use-entropy").prop("checked", true).trigger("change");
2461 $(".entropy").val("123456").trigger("input");
2462 });
2463 // check the entropy is shown as base 6, not as the original dice value
2464 waitForEntropyFeedback(function() {
2465 var entropyText = page.evaluate(function() {
2466 return $(".entropy-container").text();
2467 });
2468 if (entropyText.indexOf("123450") == -1) {
2469 console.log("Dice entropy is not shown to user as base 6 value");
2470 fail();
2471 }
2472 if (entropyText.indexOf("123456") > -1) {
2473 console.log("Dice entropy value is shown instead of true base 6 value");
2474 fail();
2475 }
2476 next();
2477 });
2478 });
2479 },
2480
2481 // The number of bits of entropy accumulated is shown
2482 function() {
2483 page.open(url, function(status) {
2484 //[ entropy, bits ]
2485 var tests = [
2486 [ "0000 0000 0000 0000 0000", "20" ],
2487 [ "0", "1" ],
2488 [ "0000", "4" ],
2489 [ "6", "2" ], // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
2490 [ "7", "3" ], // 7 in base 10 is 111 in base 2, no leading zeros
2491 [ "8", "4" ],
2492 [ "F", "4" ],
2493 [ "29", "6" ],
2494 [ "0A", "8" ],
2495 [ "1A", "8" ], // hex is always multiple of 4 bits of entropy
2496 [ "2A", "8" ],
2497 [ "4A", "8" ],
2498 [ "8A", "8" ],
2499 [ "FA", "8" ],
2500 [ "000A", "16" ],
2501 [ "5555", "11" ],
2502 [ "6666", "10" ], // uses dice, so entropy is actually 0000 in base 6, which is 4 lots of 2.58 bits, which is 10.32 bits (rounded down to 10 bits)
2503 [ "2227", "13" ], // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded down to 13)
2504 [ "222F", "16" ],
2505 [ "FFFF", "16" ],
2506 [ "0000101017", "33" ], // 10 events at 3.32 bits per event
2507 [ "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "225" ], // cards are not replaced, so a full deck is not 52^52 entropy which is 296 bits, it's 52!, which is 225 bits
2508 ]
2509 // use entropy
2510 page.evaluate(function(e) {
2511 $(".use-entropy").prop("checked", true).trigger("change");
2512 });
2513 // Run each test
2514 var nextTest = function runNextTest(i) {
2515 var entropy = tests[i][0];
2516 var expected = tests[i][1];
2517 // set entropy
2518 page.evaluate(function(e) {
2519 $(".entropy").val(e).trigger("input");
2520 }, entropy);
2521 // check the number of bits of entropy is shown
2522 waitForEntropyFeedback(function() {
2523 var entropyText = page.evaluate(function() {
2524 return $(".entropy-container").text();
2525 });
2526 if (entropyText.replace(/\s/g,"").indexOf("Bits" + expected) == -1) {
2527 console.log("Accumulated entropy is not shown correctly for " + entropy);
2528 fail();
2529 }
2530 var isLastTest = i == tests.length - 1;
2531 if (isLastTest) {
2532 next();
2533 }
2534 else {
2535 runNextTest(i+1);
2536 }
2537 });
2538 }
2539 nextTest(0);
2540 });
2541 },
2542
2543 // There is feedback provided about the supplied entropy
2544 function() {
2545 page.open(url, function(status) {
2546 var tests = [
2547 {
2548 entropy: "A",
2549 filtered: "A",
2550 type: "hexadecimal",
2551 events: 1,
2552 bits: 4,
2553 words: 0,
2554 strength: "extremely weak",
2555 },
2556 {
2557 entropy: "AAAAAAAA",
2558 filtered: "AAAAAAAA",
2559 type: "hexadecimal",
2560 events: 8,
2561 bits: 32,
2562 words: 3,
2563 strength: "extremely weak",
2564 },
2565 {
2566 entropy: "AAAAAAAA B",
2567 filtered: "AAAAAAAAB",
2568 type: "hexadecimal",
2569 events: 9,
2570 bits: 36,
2571 words: 3,
2572 strength: "extremely weak",
2573 },
2574 {
2575 entropy: "AAAAAAAA BBBBBBBB",
2576 filtered: "AAAAAAAABBBBBBBB",
2577 type: "hexadecimal",
2578 events: 16,
2579 bits: 64,
2580 words: 6,
2581 strength: "very weak",
2582 },
2583 {
2584 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
2585 filtered: "AAAAAAAABBBBBBBBCCCCCCCC",
2586 type: "hexadecimal",
2587 events: 24,
2588 bits: 96,
2589 words: 9,
2590 strength: "weak",
2591 },
2592 {
2593 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
2594 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD",
2595 type: "hexadecimal",
2596 events: 32,
2597 bits: 128,
2598 words: 12,
2599 strength: "easily cracked",
2600 },
2601 {
2602 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA",
2603 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDA",
2604 type: "hexadecimal",
2605 events: 32,
2606 bits: 128,
2607 words: 12,
2608 strength: "strong",
2609 },
2610 {
2611 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE",
2612 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEE",
2613 type: "hexadecimal",
2614 events: 40,
2615 bits: 160,
2616 words: 15,
2617 strength: "very strong",
2618 },
2619 {
2620 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE FFFFFFFF",
2621 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEEFFFFFFFF",
2622 type: "hexadecimal",
2623 events: 48,
2624 bits: 192,
2625 words: 18,
2626 strength: "extremely strong",
2627 },
2628 {
2629 entropy: "7d",
2630 type: "card",
2631 events: 1,
2632 bits: 5,
2633 words: 0,
2634 strength: "extremely weak",
2635 },
2636 {
2637 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2638 type: "card (full deck)",
2639 events: 52,
2640 bits: 225,
2641 words: 21,
2642 strength: "extremely strong",
2643 },
2644 {
2645 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
2646 type: "card (full deck, 1 duplicate: 3d)",
2647 events: 53,
2648 bits: 254,
2649 words: 21,
2650 strength: "extremely strong",
2651 },
2652 {
2653 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
2654 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
2655 events: 53,
2656 bits: 254,
2657 words: 21,
2658 strength: "extremely strong",
2659 },
2660 {
2661 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
2662 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
2663 events: 53,
2664 bits: 264,
2665 words: 24,
2666 strength: "extremely strong",
2667 },
2668 // Next test was throwing uncaught error in zxcvbn
2669 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
2670 {
2671 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2672 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
2673 events: 104,
2674 bits: 499,
2675 words: 45,
2676 strength: "extremely strong",
2677 },
2678 // Case insensitivity to duplicate cards
2679 {
2680 entropy: "asAS",
2681 type: "card (1 duplicate: AS)",
2682 events: 2,
2683 bits: 9,
2684 words: 0,
2685 strength: "extremely weak",
2686 },
2687 {
2688 entropy: "ASas",
2689 type: "card (1 duplicate: as)",
2690 events: 2,
2691 bits: 9,
2692 words: 0,
2693 strength: "extremely weak",
2694 },
2695 // Missing cards are detected
2696 {
2697 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2698 type: "card (1 missing: 9C)",
2699 events: 51,
2700 bits: 221,
2701 words: 18,
2702 strength: "extremely strong",
2703 },
2704 {
2705 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2706 type: "card (2 missing: 9C 5D)",
2707 events: 50,
2708 bits: 216,
2709 words: 18,
2710 strength: "extremely strong",
2711 },
2712 {
2713 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2714 type: "card (4 missing: 9C 5D QD...)",
2715 events: 48,
2716 bits: 208,
2717 words: 18,
2718 strength: "extremely strong",
2719 },
2720 // More than six missing cards does not show message
2721 {
2722 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
2723 type: "card",
2724 events: 45,
2725 bits: 195,
2726 words: 18,
2727 strength: "extremely strong",
2728 },
2729 // Multiple decks of cards increases bits per event
2730 {
2731 entropy: "3d",
2732 events: 1,
2733 bits: 4,
2734 bitsPerEvent: 4.34,
2735 },
2736 {
2737 entropy: "3d3d",
2738 events: 2,
2739 bits: 9,
2740 bitsPerEvent: 4.80,
2741 },
2742 {
2743 entropy: "3d3d3d",
2744 events: 3,
2745 bits: 15,
2746 bitsPerEvent: 5.01,
2747 },
2748 {
2749 entropy: "3d3d3d3d",
2750 events: 4,
2751 bits: 20,
2752 bitsPerEvent: 5.14,
2753 },
2754 {
2755 entropy: "3d3d3d3d3d",
2756 events: 5,
2757 bits: 26,
2758 bitsPerEvent: 5.22,
2759 },
2760 {
2761 entropy: "3d3d3d3d3d3d",
2762 events: 6,
2763 bits: 31,
2764 bitsPerEvent: 5.28,
2765 },
2766 {
2767 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
2768 events: 33,
2769 bits: 184,
2770 bitsPerEvent: 5.59,
2771 strength: 'easily cracked - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
2772 },
2773 ];
2774 // use entropy
2775 page.evaluate(function() {
2776 $(".use-entropy").prop("checked", true).trigger("change");
2777 $(".mnemonic-length").val("raw");
2778 });
2779 var nextTest = function runNextTest(i) {
2780 function getFeedbackError(expected, actual) {
2781 if ("filtered" in expected && actual.indexOf(expected.filtered) == -1) {
2782 return "Filtered value not in feedback";
2783 }
2784 if ("type" in expected && actual.indexOf(expected.type) == -1) {
2785 return "Entropy type not in feedback";
2786 }
2787 if ("events" in expected && actual.indexOf(expected.events) == -1) {
2788 return "Event count not in feedback";
2789 }
2790 if ("bits" in expected && actual.indexOf(expected.bits) == -1) {
2791 return "Bit count not in feedback";
2792 }
2793 if ("strength" in expected && actual.indexOf(expected.strength) == -1) {
2794 return "Strength not in feedback";
2795 }
2796 if ("bitsPerEvent" in expected && actual.indexOf(expected.bitsPerEvent) == -1) {
2797 return "bitsPerEvent not in feedback";
2798 }
2799 return false;
2800 }
2801 test = tests[i];
2802 page.evaluate(function(e) {
2803 $(".addresses").empty();
2804 $(".phrase").val("");
2805 $(".entropy").val(e).trigger("input");
2806 }, test.entropy);
2807 waitForEntropyFeedback(function() {
2808 var mnemonic = page.evaluate(function() {
2809 return $(".phrase").val();
2810 });
2811 // Check mnemonic length
2812 if ("words" in test && test.words == 0) {
2813 if (mnemonic.length > 0) {
2814 console.log("Mnemonic length for " + test.strength + " strength is not " + test.words);
2815 console.log("Entropy: " + test.entropy);
2816 console.log("Mnemonic: " + mnemonic);
2817 fail();
2818 }
2819 }
2820 else if ("words" in test) {
2821 if (mnemonic.split(" ").length != test.words) {
2822 console.log("Mnemonic length for " + test.strength + " strength is not " + test.words);
2823 console.log("Entropy: " + test.entropy);
2824 console.log("Mnemonic: " + mnemonic);
2825 fail();
2826 }
2827 }
2828 // check feedback
2829 var feedback = page.evaluate(function() {
2830 return $(".entropy-container").text();
2831 });
2832 var feedbackError = getFeedbackError(test, feedback);
2833 if (feedbackError) {
2834 console.log("Entropy feedback for " + test.entropy + " returned error");
2835 console.log(feedbackError);
2836 fail();
2837 }
2838 // Run next test
2839 var isLastTest = i == tests.length - 1;
2840 if (isLastTest) {
2841 next();
2842 }
2843 else {
2844 runNextTest(i+1);
2845 }
2846 });
2847 }
2848 nextTest(0);
2849 });
2850 },
2851
2852 // Entropy is truncated from the left
2853 function() {
2854 page.open(url, function(status) {
2855 var expected = "avocado zoo zone";
2856 // use entropy
2857 page.evaluate(function() {
2858 $(".use-entropy").prop("checked", true).trigger("change");
2859 $(".mnemonic-length").val("raw");
2860 var entropy = "00000000 00000000 00000000 00000000";
2861 entropy += "11111111 11111111 11111111 1111"; // Missing last byte
2862 $(".entropy").val(entropy).trigger("input");
2863 });
2864 // check the entropy is truncated from the right
2865 waitForGenerate(function() {
2866 var actual = page.evaluate(function() {
2867 return $(".phrase").val();
2868 });
2869 if (actual != expected) {
2870 console.log("Entropy is not truncated from the right");
2871 console.log("Expected: " + expected);
2872 console.log("Got: " + actual);
2873 fail();
2874 }
2875 next();
2876 });
2877 });
2878 },
2879
2880 // Very large entropy results in very long mnemonics
2881 function() {
2882 page.open(url, function(status) {
2883 // use entropy
2884 page.evaluate(function() {
2885 $(".use-entropy").prop("checked", true).trigger("change");
2886 $(".mnemonic-length").val("raw");
2887 var entropy = "";
2888 // Generate a very long entropy string
2889 for (var i=0; i<33; i++) {
2890 entropy += "AAAAAAAA"; // 3 words * 33 iterations = 99 words
2891 }
2892 $(".entropy").val(entropy).trigger("input");
2893 });
2894 // check the mnemonic is very long
2895 waitForGenerate(function() {
2896 var wordCount = page.evaluate(function() {
2897 return $(".phrase").val().split(" ").length;
2898 });
2899 if (wordCount != 99) {
2900 console.log("Large entropy does not generate long mnemonic");
2901 console.log("Expected 99 words, got " + wordCount);
2902 fail();
2903 }
2904 next();
2905 });
2906 });
2907 },
2908
2909 // Is compatible with bip32jp entropy
2910 // https://bip32jp.github.io/english/index.html
2911 // NOTES:
2912 // Is incompatible with:
2913 // base 20
2914 function() {
2915 page.open(url, function(status) {
2916 var expected = "train then jungle barely whip fiber purpose puppy eagle cloud clump hospital robot brave balcony utility detect estate old green desk skill multiply virus";
2917 // use entropy
2918 page.evaluate(function() {
2919 $(".use-entropy").prop("checked", true).trigger("change");
2920 $(".mnemonic-length").val("raw");
2921 var entropy = "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
2922 $(".entropy").val(entropy).trigger("input");
2923 });
2924 // check the mnemonic matches the expected value from bip32jp
2925 waitForGenerate(function() {
2926 var actual = page.evaluate(function() {
2927 return $(".phrase").val();
2928 });
2929 if (actual != expected) {
2930 console.log("Mnemonic does not match bip32jp for base 6 entropy");
2931 console.log("Expected: " + expected);
2932 console.log("Got: " + actual);
2933 fail();
2934 }
2935 next();
2936 });
2937 });
2938 },
2939
2940 // Blank entropy does not generate mnemonic or addresses
2941 function() {
2942 page.open(url, function(status) {
2943 // use entropy
2944 page.evaluate(function() {
2945 $(".use-entropy").prop("checked", true).trigger("change");
2946 $(".entropy").val("").trigger("input");
2947 });
2948 waitForFeedback(function() {
2949 // check there is no mnemonic
2950 var phrase = page.evaluate(function() {
2951 return $(".phrase").val();
2952 });
2953 if (phrase != "") {
2954 console.log("Blank entropy does not result in blank mnemonic");
2955 console.log("Got: " + phrase);
2956 fail();
2957 }
2958 // check there are no addresses displayed
2959 var addresses = page.evaluate(function() {
2960 return $(".address").length;
2961 });
2962 if (addresses != 0) {
2963 console.log("Blank entropy does not result in zero addresses");
2964 fail();
2965 }
2966 // Check the feedback says 'blank entropy'
2967 var feedback = page.evaluate(function() {
2968 return $(".feedback").text();
2969 });
2970 if (feedback != "Blank entropy") {
2971 console.log("Blank entropy does not show feedback message");
2972 fail();
2973 }
2974 next();
2975 });
2976 });
2977 },
2978
2979 // Mnemonic length can be selected even for weak entropy
2980 function() {
2981 page.open(url, function(status) {
2982 // use entropy
2983 page.evaluate(function() {
2984 $(".use-entropy").prop("checked", true).trigger("change");
2985 $(".entropy").val("012345");
2986 $(".mnemonic-length").val("18").trigger("change");
2987 });
2988 // check the mnemonic is the correct length
2989 waitForGenerate(function() {
2990 var phrase = page.evaluate(function() {
2991 return $(".phrase").val();
2992 });
2993 var numberOfWords = phrase.split(/\s/g).length;
2994 if (numberOfWords != 18) {
2995 console.log("Weak entropy cannot be overridden to give 18 word mnemonic");
2996 console.log(phrase);
2997 fail();
2998 }
2999 next();
3000 });
3001 });
3002 },
3003
3004 // Github issue 33
3005 // https://github.com/iancoleman/bip39/issues/33
3006 // Final cards should contribute entropy
3007 function() {
3008 page.open(url, function(status) {
3009 // use entropy
3010 page.evaluate(function() {
3011 $(".use-entropy").prop("checked", true).trigger("change");
3012 $(".mnemonic-length").val("raw");
3013 $(".entropy").val("7S 9H 9S QH 8C KS AS 7D 7C QD 4S 4D TC 2D 5S JS 3D 8S 8H 4C 3C AC 3S QC 9C JC 7H AD TD JD 6D KH 5C QS 2S 6S 6H JH KD 9D-6C TS TH 4H KC 5H 2H AH 2C 8D 3H 5D").trigger("input");
3014 });
3015 // get the mnemonic
3016 waitForGenerate(function() {
3017 var originalPhrase = page.evaluate(function() {
3018 return $(".phrase").val();
3019 });
3020 // Set the last 12 cards to be AS
3021 page.evaluate(function() {
3022 $(".addresses").empty();
3023 $(".entropy").val("7S 9H 9S QH 8C KS AS 7D 7C QD 4S 4D TC 2D 5S JS 3D 8S 8H 4C 3C AC 3S QC 9C JC 7H AD TD JD 6D KH 5C QS 2S 6S 6H JH KD 9D-AS AS AS AS AS AS AS AS AS AS AS AS").trigger("input");
3024 });
3025 // get the new mnemonic
3026 waitForGenerate(function() {
3027 var newPhrase = page.evaluate(function() {
3028 return $(".phrase").val();
3029 });
3030 // check the phrase has changed
3031 if (newPhrase == originalPhrase) {
3032 console.log("Changing last 12 cards does not change mnemonic");
3033 console.log("Original:");
3034 console.log(originalPhrase);
3035 console.log("New:");
3036 console.log(newPhrase);
3037 fail();
3038 }
3039 next();
3040 });
3041 });
3042 });
3043 },
3044
3045
3046 // If you wish to add more tests, do so here...
3047
3048 // Here is a blank test template
3049 /*
3050
3051 function() {
3052 page.open(url, function(status) {
3053 // Do something on the page
3054 page.evaluate(function() {
3055 $(".phrase").val("abandon abandon ability").trigger("input");
3056 });
3057 waitForGenerate(function() {
3058 // Check the result of doing the thing
3059 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3060 var actual = page.evaluate(function() {
3061 return $(".address:first").text();
3062 });
3063 if (actual != expected) {
3064 console.log("A specific message about what failed");
3065 console.log("Expected: " + expected);
3066 console.log("Actual: " + actual);
3067 fail();
3068 }
3069 // Run the next test
3070 next();
3071 });
3072 });
3073 },
3074
3075 */
3076
3077 ];
3078
3079 console.log("Running tests...");
3080 tests = shuffle(tests);
3081 next();