More Secrets of JavaScript Libraries

  • 17,598 views
Uploaded on

A talk given by Nate Koechley (of YUI), Andrew Dupont (of Prototype), Becky Gibson (of Dojo), and myself (jQuery) at the 2009 SXSW.

A talk given by Nate Koechley (of YUI), Andrew Dupont (of Prototype), Becky Gibson (of Dojo), and myself (jQuery) at the 2009 SXSW.

More in: Technology , Education
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
17,598
On Slideshare
0
From Embeds
0
Number of Embeds
5

Actions

Shares
Downloads
780
Comments
2
Likes
57

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. More Secrets of JavaScript Libraries Nate Koechley, Andrew Dupont, Becky Gibson, John Resig 4x 10min talks with Q&A at the end.
  • 2. Getting Loaded Easier development. Better performance.
  • 3. Get & Loader Easier development. Better performance.
  • 4. The Problems
  • 5. JavaScript loading blocks page rendering.
  • 6. More les increases blocking.
  • 7. We know a single le is best:
  • 8. But in practice...
  • 9. Widgets & Plugins may have various prerequisites.
  • 10. <script src=quot;jquery/jquery-1.2.2.pack.jsquot;/> <script src=quot;jquery/chili/chili.jsquot;/> <script src=quot;jquery.cookie.jsquot;/> <script src=quot;jquery.clickmenu.pack.jsquot;/> <script src=quot;jquery.columnmanager.jsquot;/>
  • 11. <script src=quot;yahoo-dom-event.jsquot;/> <script src=quot;element-min.jsquot;/> <script src=quot;connection-min.jsquot;/> <script src=quot;tabview-min.jsquot;/>
  • 12. Sites often use multiple unrelated JavaScript les.
  • 13. <script src=quot;prototype.jsquot;/> <script src=quot;scriptaculous.jsquot;/> <script src=quot;csiManager.jsquot;/> <script src=quot;StorageManager.jsquot;/> <script src=quot;main.jsquot;/> <script src=quot;urchin.jsquot;>
  • 14. Larger projects use multiple les for exibility & optimization
  • 15. <script src=quot;yui-base.jsquot;/> <script src=quot;oop.jsquot;/> <script src=quot;event.jsquot;/> <script src=quot;attribute.jsquot;/> <script src=quot;base.jsquot;/> <script src=quot;dom.jsquot;/> <script src=quot;node.jsquot;/> <script src=quot;widget.jsquot;/> <script src=quot;dd.jsquot;/> <script src=quot;slider/slider.js/quot;>
  • 16. <script src=quot;dd.jsquot;/> dd-ddm-base dd-ddm dd-drag dd dd-proxy dd-constrain dd-drop dd-plugin dd-drop-plugin
  • 17. Most les are order-dependent.
  • 18. Other les aren’t needed right away.
  • 19. And it’s nice to easily toggle between -raw.js, -min.js, and -debug.js versions.
  • 20. three avors of each
  • 21. fully-commented
  • 22. -debug with logging
  • 23. -min for deployment
  • 24. So, our goals:
  • 25. Features • Handle prerequisies without duplication • Manage order dependency • Per-module exibility • Fast toggling between avors • Continue fetching onDemand • Minimize HTTP requests and not block rendering
  • 26. That’s what YUI’s Get & Loader Utilities do.
  • 27. They’re partners.
  • 28. Loader Get
  • 29. Combo Loader Get Handler
  • 30. Loader Get Combo The “seed” le. Parses request. Creates new nodes & src’s. Meta data. Concats modules if rst. Non-blocking. Con g options. Edge-caches. Cross-domain. Helpers/Sugar. GET not POST.
  • 31. Loads anything: • Library les • Your own les • JS, CSS, JSON, ... • Urchin.js, badges, includes, ...
  • 32. Easily de ne your resources
  • 33. // one or more external modules // that can be loaded along side of YUI modules: { json_org: { fullpath: quot;http://www.json.org/json.jsquot; }, json2_org: { fullpath: quot;http://www.json.org/json2.jsquot; } }
  • 34. And their relationships
  • 35. modules: { dom: { requires: ['event'], submodules: { 'dom-base': { requires: ['event'] }, 'dom-style': { requires: ['dom-base'] }, 'dom-screen': { requires: ['dom-base', 'dom-style'] }, selector: { requires: ['dom-base'] } } },
  • 36. With ne-grained control
  • 37. name type path fullpath requires optional supersedes after rollup
  • 38. Once the seed is on the page:
  • 39. <script src=quot;yui-min.jsquot;/> <script> YUI().use(quot;sliderquot;, function(Y) { // Slider available and ready for use. }); </script>
  • 40. Other cool features in Get:
  • 41. Choose where the nodes go.
  • 42. Purge After Reading
  • 43. Bene ts • Easy to use. • No typos. Less to manage. • Much faster performance. • Extensive exibility. • Nice support for lazy-loading. • Library agnostic.
  • 44. nate@koechley.com developer.yahoo.com/yui/3/yui/ developer.yahoo.com/yui/3/get/ github.com/yui
  • 45. Meta-Language Frameworks
  • 46. The code you write is transformed before a browser consumes it
  • 47. Google Web Toolkit (turns Java into JavaScript)
  • 48. Pyjamas (turns Python into JavaScript)
  • 49. Cappuccino (turns “Objective-J” into JavaScript)
  • 50. Some of them are lightweight...
  • 51. Narrative JS (adds “sleep”-like functionality to JS)
  • 52. function waitForButton() { // do some work // create our notifier var notifier = new EventNotifier(); // attach our notifier to the button document.getElementById(quot;myButtonquot;).onclick = notifier; // wait for the button to be clicked notifier.wait->(); // do more work }
  • 53. Google Caja (turns JavaScript into safer JavaScript)
  • 54. var blogComment = document.createElement('div'); blogComment.innerHTML = quot;<b>user entered text quot; + quot;which happens to contain a script quot; + quot;tag.</b><script defer>alert('muahahaa');</scrquot; + quot;ipt>quot;; document.getElementById(quot;resultquot;).appendChild(blogComment);
  • 55. ___.loadModule({ 'instantiate': function (___, IMPORTS___) { var moduleResult___ = ___.NO_RESULT; var $v = ___.readImport(IMPORTS___, '$v', { 'getOuters': { '()': { } }, 'ro': { '()': { } }, 'so': { '()': { } }, 'initOuter': { '()': { } }, 'cm': { '()': { } }, 's': { '()': { } } }); var $dis = $v.getOuters(); $v.initOuter('onerror'); try { {
  • 56. $v.so('blogComment', $v.cm($v.ro('document'), 'createElement', [ 'div' ])); $v.s($v.ro('blogComment'), 'innerHTML', '<b>user entered text which happens to contain a script ' + 'tag.</b><script defer>alert('muahahaa');</scr' + 'ipt>'); moduleResult___ = $v.cm($v.cm($v.ro('document'), 'getElementById', [ 'result' ]), 'appendChild', [ $v.ro('blogComment') ]); } } catch (ex___) { ___.getNewModuleHandler().handleUncaughtException(ex___, $v.ro('onerror'), 'testbed/', '2'); } return moduleResult___; }, 'cajolerName': 'com.google.caja', 'cajolerVersion': '3339M', 'cajoledDate': 1237011597543 }); }
  • 57. Why?
  • 58. GWT gets to use the Java toolset
  • 59. Cappuccino gets to introduce new syntax and new features
  • 60. Let’s say you want Ruby-style “catch-all” methods
  • 61. object.methodName(arg);
  • 62. object.callMethod(quot;methodNamequot;, arg);
  • 63. [object methodName];
  • 64. Fine, but now you’re more distant from the code
  • 65. Harder to debug
  • 66. Longer feedback loop
  • 67. It’s a thicker abstraction
  • 68. John’s blog post http://is.gd/bKwl
  • 69. Abstractions leak... what happens when stu goes wrong?
  • 70. Francisco’s Response http://is.gd/aJ36
  • 71. All frameworks are abstractions
  • 72. Yeah, abstractions leak, but we all use them anyway
  • 73. MORAL:
  • 74. Abstractions are trade-o s
  • 75. Thicker abstractions have more hassle, but o er greater rewards
  • 76. Which way should you go?
  • 77. Think about up-front cost and ongoing cost
  • 78. Use what makes sense to your head
  • 79. Consider: We’re “stuck” with JavaScript
  • 80. !quot;#$%&'($)%*++quot;,,-.-$-/0 1quot;+20%3-.,'4 5'6'%*++quot;,,-.-$-/0%7quot;#) 819%&quot;.%*++quot;,,-.-$-/0%*(+:-/quot;+/
  • 81. <'=%>%!quot;#,'4,%?quot;'=$quot;%5'4@/%8A=$quot;Aquot;4/ % *++quot;,,-.-$-/0 ;
  • 82. >B%90%,-/quot;%8C%#++quot;,,-.$quot; %D%-/@,%E=%;FGHI >
  • 83. >B%90%,-/quot;%8C%#++quot;,,-.$quot; %D%-/@,%E=%;FGHI F
  • 84. ;B%?quot;'=$quot;%K-/:%5-,#.-$-/-quot;,%)'4@/%E,quot;%A0 % ,-/quot; J
  • 85. ;B%?quot;'=$quot;%K-/:%5-,#.-$-/-quot;,%)'4@/%E,quot;%A0 % ,-/quot; L
  • 86. MB%*))-4N%*MM0%-,%/''%:#()%#4)%K-$$%(E-4% A0%)quot;,-N4 H
  • 87. MB%*))-4N%*MM0%-,%/''%:#()%#4)%K-$$%(E-4% A0%)quot;,-N4 O
  • 88. MB%*))-4N%*MM0%-,%/''%:#()%#4)%K-$$%(E-4% A0%)quot;,-N4 -,%)'-4N%-/ P
  • 89. MB%*))-4N%*MM0%-,%/''%:#()%#4)%K-$$%(E-4% A0%)quot;,-N4 -,%)'-4N%-/ -,%)'-4N%-/ MQ
  • 90. MB%*))-4N%*MM0%-,%/''%:#()%#4)%K-$$%(E-4% A0%)quot;,-N4 -,%)'-4N%-/ -,%)'-4N%-/ R8%-,%)'-4N%-/ MM
  • 91. *$$%/:quot;,quot;%+'A=#4-quot;,%#(quot;%)'-4N%-/ M;
  • 92. *!8*%D *++quot;,,-.$quot;%!-+:%84/quot;(4quot;/%*==$-+#/-'4, % M>
  • 93. *!8*%D%&:#/%-,%-/S ! *++quot;,,-.$quot;%!-+:%84/quot;(4quot;/%*==$-+#/-'4, ! &>T%C=quot;+-U-+#/-'4V%$-2quot;%W<97V%TCCV%X97%quot;/+B ! &-/:-4%?('/'+'$,%Y%Z'(A#/,%&'(2-4N%3('E=%K:-+:%-, % =#(/%'U%&*8%D%&quot;.%*++quot;,,-.-$-/0%84-/-#/-[quot; ! 84%7#,/%T#$$%C/#/E, ! 8A=$quot;Aquot;4/quot;)%-4%Z-(quot;U'GV%8O%K-/:%]=quot;(#%#4)%C#U#(- % E4)quot;(%)quot;[quot;$'=Aquot;4/ ! 3#-4-4N%-4+(quot;#,-4N%,E=='(/%.0%.('K,quot;(,V%&quot;.%/''$2-/,% #4)%#,,-,/-[quot;%/quot;+:4'$'N-quot;, MF
  • 94. *!8*%][quot;([-quot;K ! *))%('$quot;%,quot;A#4/-+,%/'%,+(-=/quot;)%R8%quot;$quot;Aquot;4/, ! R=)#/quot;%,/#/quot;%-4U'(A#/-'4%)04#A-+#$$0 ! 9#2quot;%-/quot;A,%U'+E,#.$quot;%[-#%/#.-4)quot;G%#//(-.E/quot; ! *))%2quot;0.'#()%quot;[quot;4/%:#4)$-4N ^ 9-A-+%/:quot;%2quot;0.'#()%.quot;:#[-'(%'U%/:quot;%(-+:%+$-quot;4/%R8 ^ 9-4-A-_quot;%/#.%2quot;0%4#[-N#/-'4 ! *))%$-[quot;%(quot;N-'4%-4U'%#4)%4'/-U-+#/-'4%/'%,E=='(/%*6#G MJ
  • 95. *!8*%G#A=$quot;%D%<(quot;quot; !'$quot;%`%/(quot;quot; a'4%'E/quot;(%+'4/#-4quot;(b !'$quot;%` % /(quot;quot;-/quot;A quot;G=#4)quot;)`/(Equot; a'4%'=quot;4%*U(-+#%4')quot;b !'$quot;%`%/(quot;quot;-/quot;A ,quot;$quot;+/quot;)`/(Equot; a'4%:-N:$-N:/quot;)%N0=/%+:-$)%4')quot;%K-/:%4'%+:-$)(quot;4b !'$quot;%`%/(quot;quot;-/quot;A quot;G=#4)quot;)`U#$,quot; a'4%+$',quot;)%*E,/(#$-#%4')quot;b ML
  • 96. *!8*%!'$quot;, ! $-42 ! #==$-+#/-'4 ! +'A.'.'GV%'=/-'4 ! =(quot;,quot;4/#/-'4 ! +:quot;+2.'G ! N('E= ! (#)-'V%(#)-'N('E= ! N(-)V%N(-)+quot;$$ ! .E//'4 ! /#.V%/#.+'4/#-4quot;(V% /#.$-,/V%/#.=#4quot;$ ! =('N(quot;,,.#( ! $-,/V%$-,/-/quot;A ! ,$-)quot;( ! Aquot;4E.#(V%Aquot;4E ! ,=-4.E//'4 ! /''$.#( ! /(quot;quot;V%/(quot;quot;-/quot;A ! A'(quot;cc ! #$quot;(/ MH
  • 97. *!8*D%%C/#/quot;, C/#/quot; e#$Equot;, +:quot;+2quot;) /(Equot;%d%U#$,quot;%d%A-Gquot;) )-,#.$quot;) /(Equot;%d%U#$,quot; (quot;#)'4$0 /(Equot;%d%U#$,quot; quot;G=#4)quot;) /(Equot;%d%U#$,quot; [#$Equot;A-4V%[#$Equot;A#GV % T5*<* [#$Equot;4'K 'K4,V%:#,='=E= 85!Z )quot;,+(-.quot;).0B%$#.quot;$$quot;).0 85!Z 9#40%A'(quot;%ccB MO
  • 98. *!8*%7#4)A#(2%!'$quot;, ! 9#2quot;,%U-4)-4N%#4)%4#[-N#/-4N%/'%,quot;+/-'4,%'U%/:quot;%=#Nquot; % quot;#,-quot;( !quot;quot;#$%&'$() ^ *&))+, ^ -(.quot;#+.+)'&,/ ^ -()'+)'$)0( ^ 1&$) ^ 2&3$4&'$() ^ 5+&,%6 ^ MP
  • 99. 7#4)A#(2,%G#A=$quot; ;Q
  • 100. 7#4)A#(2,%G#A=$quot; .#44quot;( ;M
  • 101. 7#4)A#(2,%G#A=$quot; .#44quot;( f#[-N#/-'4 ;;
  • 102. 7#4)A#(2,%G#A=$quot; .#44quot;( f#[-N#/-'4 9#-4 ;>
  • 103. 7#4)A#(2,%G#A=$quot; .#44quot;( f#[-N#/-'4 9#-4 +'4/quot;4/-4U' ;F
  • 104. 7#4)A#(2%G#A=$quot; g)-[%)'6'<0=quot;`h)-6-/B$#0'E/BT'4/quot;4/?#4quot;h%(quot;N-'4`h/'=h % +$#,,`h.#44quot;(h%('$quot;`h.#44quot;(hi g,=#4%+$#,,`h$'N'hi&quot;.*MM0gj,=#4i % %% gj)-[igIDD%quot;4)%'U%/'=%DDi % %% g)-[%-)`h$quot;U/h%)'6'<0=quot;`h)-6-/B$#0'E/BT'4/quot;4/?#4quot;h%(quot;N-'4`h$quot;U/h % % ('$quot;`h4#[-N#/-'4hi gIDD%<(quot;quot;%N'quot;,%:quot;(quot;%DDi %% gj)-[igIDD%quot;4)%'U%$quot;U/%DDi % %% g)-[%-)`h+'4/quot;4/h%)'6'<0=quot;`h)-6-/B$#0'E/BT'4/quot;4/?#4quot;h%/-/$quot;`hT'4/quot;4/h % % ('$quot;`hA#-4h%#(-#D$-[quot;`h#,,quot;(/-[quot;h%#(-#D#/'A-+`h/(Equot;h%i %%%%%%%% 84U'%U('A%,quot;$quot;+/quot;)%/(quot;quot;%-/quot;A%-,%$'#)quot;)%:quot;(quot; %% gj)-[igIDD%quot;4)%'U%+quot;4/quot;(%DDi % %% g)-[%)'6'<0=quot;`h)-6-/B$#0'E/BT'4/quot;4/?#4quot;h%(quot;N-'4`h.'//'Ah % % ('$quot;`h+'4/quot;4/-4U'h i % gIDD%U''/quot;(%N'quot;,%:quot;(quot;%DDi %% gj)-[igIDD%quot;4)%'U%.'//'A%DDi % ;J
  • 105. *!8*%7-[quot;%!quot;N-'4, ! ?quot;(+quot;-[#.$quot;%,quot;+/-'4,%#(quot;%-)quot;4/-U-quot;)%K-/:%(quot;N-'4%('$quot; ! 7-[quot;%-4)-+#/quot;,%(quot;N-'4%-,%E=)#/quot;) ^ e#$Equot;,%'Uk%]UUV%?'$-/quot;V%*,,quot;(/-[quot;V%!E)quot; ! */'A-+%-)quot;4/-U-quot;,%/:quot;%quot;G/quot;4/%'U%E=)#/quot;, ^ <(Equot;%^%quot;4/-(quot;%(quot;N-'4%-,%E=)#/quot;)%#4)%(quot;$quot;[#4/ ^ Z#$,quot;%^%'4$0%+:#4Nquot;)%quot;$quot;Aquot;4/%4quot;quot;),%/'%.quot;%=(quot;,quot;4/quot;)%/'%E,quot;( ;L
  • 106. 7-[quot;%!quot;N-'4%G#A=$quot; ;H
  • 107. 7-[quot;%!quot;N-'4%G#A=$quot; gIDD%Aquot;,,#Nquot;%=(quot;[-quot;K%=#4quot;%DDi g)-[%-)`hAquot;,,#Nquot;h%)'6'<0=quot;`h)-6-/B$#0'E/BT'4/quot;4/?#4quot;h % (quot;N-'4`h+quot;4/quot;(h%A-4C-_quot;`h;Qh ('$quot;`h(quot;N-'4h%#(-#D$-[quot;`h#,,quot;(/-[quot;h%#(-#D#/'A-+`h/(Equot;h%i 9quot;,,#Nquot;%T'4/quot;4/,%$'#)quot;)%:quot;(quot; gj)-[i% gIDD%quot;4)%'U%hAquot;,,#Nquot;h%DDi ;O
  • 108. CEAA#(0 ! lC%<''$2-/,%#(quot;%-A=$quot;Aquot;4/-4N%*!8*%D%E,quot;%/:quot;AI ^ 5'6'%)-6-/,%#(quot;%#$$%UE$$0%#++quot;,,-.$quot; ! *!8*%A#2quot;,%*6#G%#++quot;,,-.$quot; ! 9#2quot;%0'E(%Kquot;.,-/quot;,%)04#A-+%*f5%#++quot;,,-.$quot;I 8A='(/#4+quot;%'U%mquot;0.'#() ;P
  • 109. Performance and Testing John Resig
  • 110. Performance
  • 111. Analyzing Performance Optimizing performance is a huge ! concern: Faster code = happy users! Measure execution time ! Loop the code a few times ! Measure the di!erence: ! ! quot;new Date#.getTimequot;#;
  • 112. Stack Pro$ling jQuery Stack Pro$ler ! Look for problematic methods and plugins ! http://ejohn.org/blog/deep%pro$ling% ! jquery%apps/
  • 113. Accuracy of JavaScript Time We’re measuring the performance of JavaScript from within JavaScript! http://ejohn.org/blog/accuracy-of-javascript-time/
  • 114. 15ms intervals ONLY! Error Rate of 50-750%!
  • 115. Performance Tools How can we get good numbers? ! We have to go straight to the source: Use ! the tools the browsers provide. Tools: ! ! Firebug Pro$ler ! Safari Pro$ler ! quot;Part of Safari 4# ! IE 8 Pro$ler
  • 116. Firebug Pro$ler
  • 117. Safari 4 Pro$ler
  • 118. IE 8 Pro$ler
  • 119. FireUnit A simple JavaScript test suite embedded in ! Firebug. http://$reunit.org/ !
  • 120. FireUnit Pro$le Data { fireunit.getProfile(); quot;timequot;: 8.443, quot;callsquot;: 611, quot;dataquot;:[ { quot;namequot;:quot;makeArray()quot;, quot;callsquot;:1, quot;percentquot;:23.58, quot;ownTimequot;:1.991, quot;timequot;:1.991, quot;avgTimequot;:1.991, quot;minTimequot;:1.991, quot;maxTimequot;:1.991, quot;fileNamequot;:quot;jquery.js (line 2059)quot; }, // etc. http://ejohn.org/blog/function-call-profiling/ ]}
  • 121. Complexity Analysis Analyze complexity rather than raw time ! jQuery Call Count Pro$ler quot;uses FireUnit# ! Method Calls Big-O .addClass(quot;testquot;); 542 6n .addClass(quot;testquot;); 592 6n .removeClass(quot;testquot;); 754 8n .removeClass(quot;testquot;); 610 6n .css(quot;colorquot;, quot;redquot;); 495 5n .css({color: quot;redquot;, border: quot;1px 887 9n solid redquot;}); .remove(); 23772 2n+n2 .append(quot;<p>test</p>quot;); 307 3n
  • 122. Complexity Analysis Reducing call count helps to reduce ! complexity Results for 1.3.3: ! Method Calls Big-O .remove(); 298 3n .html(quot;<p>test</p>quot;); 507 5n .empty(); 200 2n http://ejohn.org/blog/function-call-profiling/
  • 123. Testing
  • 124. Test Suites Automated testing ! jQuery, Prototype, Dojo, YUI all have ! their own test suites
  • 125. QUnit jQuery&s Test Suite ! ! Nice and simple ! Works well for asynchronous tests, too.
  • 126. qUnit Usage test(quot;a basic test examplequot;, function() { ! ok( true, quot;this test is finequot; ); var value = quot;helloquot;; equals( quot;helloquot;, value, quot;We expect value to be helloquot; ); }); module(quot;Module Aquot;); test(quot;first test within modulequot;, function() { ok( true, quot;all passquot; ); }); test(quot;second test within modulequot;, function() { ok( true, quot;all passquot; ); }); module(quot;Module Bquot;); test(quot;some other testquot;, function() { expect(1); ok( true, quot;wellquot; ); });
  • 127. qUnit Output
  • 128. Choose Your Browsers
  • 129. Cost / Bene$t IE 7 IE 6 FF 3 Safari 3 Opera 9.5 Cost Benefit Draw a line in the sand.
  • 130. Graded Support Yahoo Browser Compatibility
  • 131. Browser Support Grid IE Firefox Safari Opera Chrome Previous 6.0 2.0 3.0 9.5 Current 7.0 3.0 3.2 9.6 1.0 Next 8.0 3.1 4.0 10.0 2.0 jQuery Browser Support
  • 132. Browser Support Grid IE Firefox Safari Opera Chrome Previous 3.0 9.5 6.0 2.0 Current 7.0 3.0 3.2 9.6 1.0 Next 8.0 3.1 4.0 2.0 10.0 jQuery 1.3 Browser Support
  • 133. The Scaling Problem The Problem: ! ! jQuery has 6 test suites ! Run in 11 browsers ! quot;Not even including multiple platforms!# All need to be run for every commit, ! patch, and plugin. JavaScript testing doesn&t scale well. !
  • 134. Distributed Testing Hub server ! Clients connect and help run tests ! A simple JavaScript client that can be run ! in all browsers ! Including mobile browsers! ! TestSwarm
  • 135. FF 3.5 FF 3.5 FF 3.5 IE 6 IE 6 FF 3 IE 6 Op 9 FF 3 IE 7 TestSwarm IE 7 Test Suite Test Suite Test Suite
  • 136. Manual Testing Push tests to users who follow pre%de$ned ! steps Answer 'Yes&/&No& questions which are ! pushed back to the server. An e!ective way to distribute manual test ! load to dozens of clients.
  • 137. TestSwarm.com Incentives for top testers quot;t%shirts, books# ! Will be opening for testing at the end of ! the month Help your favorite JavaScript library ! become better tested! http://testswarm.com !
  • 138. Q&A Please come up to the microphones!