<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.7.4">Jekyll</generator><link href="http://mirko.vukusic.net/feed.xml" rel="self" type="application/atom+xml" /><link href="http://mirko.vukusic.net/" rel="alternate" type="text/html" /><updated>2019-03-12T01:42:07+00:00</updated><id>http://mirko.vukusic.net/feed.xml</id><title type="html">Mirko’s Blog</title><subtitle>Programming and general IT blog.</subtitle><author><name>Mirko Vukušić</name><uri>http://mirko.vukusic.net</uri></author><entry><title type="html">Anet A8 Tatara steel frame by Orballo printing - REVIEW</title><link href="http://mirko.vukusic.net/3d%20printing/anet-a8-tatara-steel-frame-by-orballo-printing-review/" rel="alternate" type="text/html" title="Anet A8 Tatara steel frame by Orballo printing - REVIEW" /><published>2019-03-11T14:50:37+00:00</published><updated>2019-03-11T14:50:37+00:00</updated><id>http://mirko.vukusic.net/3d%20printing/anet-a8-tatara-steel-frame-by-orballo-printing-review</id><content type="html" xml:base="http://mirko.vukusic.net/3d%20printing/anet-a8-tatara-steel-frame-by-orballo-printing-review/">&lt;p&gt;After my first steps in hobby 3d printing with Anet A8 it was time to make some serious upgrades. Was never too interested in aluminum AM8 version after I saw Tatara steel designs (&lt;a href=&quot;https://www.thingiverse.com/thing:2221391&quot;&gt;v1&lt;/a&gt;, &lt;a href=&quot;https://www.thingiverse.com/thing:2736479&quot;&gt;v1.1&lt;/a&gt;). Since shipping from USA to Europe is not cheap, I started searching for European suppliers. It was not as easy as I’ve hoped.&lt;/p&gt;

&lt;p&gt;I found some on Ebay UK for 77 GBP + 23 GBP shipping. Totalling to 116 EUR. Local cutters quoted it 200 EUR (!). Both options also leave you with raw steel surface which needs further work (polish and/or color). Then Orballo printing &lt;a href=&quot;https://orballoprinting.com&quot;&gt;website&lt;/a&gt; popped out and it offered their own version for 71 EUR + 15 EUR shipping and is in black powder coated!&lt;/p&gt;

&lt;p&gt;But let’s go from the beginning…&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#orballo-printing-service-impressions&quot; id=&quot;markdown-toc-orballo-printing-service-impressions&quot;&gt;Orballo printing service impressions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#orballos-tatara-steel-frame-derivative-for-anet-a8---first-impressions&quot; id=&quot;markdown-toc-orballos-tatara-steel-frame-derivative-for-anet-a8---first-impressions&quot;&gt;Orballo’s Tatara steel frame derivative for Anet A8 - first impressions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#assembly&quot; id=&quot;markdown-toc-assembly&quot;&gt;Assembly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#first-tests-and-impressions&quot; id=&quot;markdown-toc-first-tests-and-impressions&quot;&gt;First tests and impressions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#before-and-after-prints&quot; id=&quot;markdown-toc-before-and-after-prints&quot;&gt;“Before and after” prints&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#so-how-good-is-orballo-printing-tatara-steel-frame-for-anet-a8&quot; id=&quot;markdown-toc-so-how-good-is-orballo-printing-tatara-steel-frame-for-anet-a8&quot;&gt;So, how good is Orballo Printing Tatara steel frame for Anet A8?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#all-together-from-website-to-print---the-verdict&quot; id=&quot;markdown-toc-all-together-from-website-to-print---the-verdict&quot;&gt;All together… from website to print - the verdict&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#todo&quot; id=&quot;markdown-toc-todo&quot;&gt;TODO&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;orballo-printing-service-impressions&quot;&gt;Orballo printing service impressions&lt;/h2&gt;

&lt;p&gt;This company (Spain) was not very well known so I decided to check it online. &lt;a href=&quot;https://www.trustpilot.com/review/orballoprinting.com&quot;&gt;TrustPilot&lt;/a&gt; had more than a few very bad reviews on their service! However it got as much 5-star ratings too. Still, I was worried. While I was browsing the website, Hugo from Orballo contacted me through their website chat. That already was not in line with some reviews about non-existing support, but hey, maybe they invest time in pre-sales and not after sales. Hugo tried to explain that they had only a few issues but that they were all resolved, that they were swamped with work during that time, but still… it took me more than a month to come back and order it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nice design, practical and user friendly. However, it is rather slow. Some parts are not translated too. What I also don’t like is that prices are “tax excluded” at first. After you log in, prices change to “tax included”. Since I logged in as a company with EU VAT ID, I would expect it the other way around. I would also expect payment for EU resident companies to exclude VAT but that was not the case by default. However, I didn’t want to bother with that so I just ordered without raising the ticket.&lt;/p&gt;

&lt;p&gt;All in all I give it &lt;strong&gt;4/5&lt;/strong&gt;, partially also because of the reasons products are not described as detailed as I would like (read below).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Packaging and delivery:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;27-2-2019 (Wednesday) - I ordered and payed (late afternoon)&lt;/p&gt;

&lt;p&gt;28-2-2019 (Thursday) - Order status was “in progress”&lt;/p&gt;

&lt;p&gt;1-3-2019 (Friday) - order was in “shipped” status&lt;/p&gt;

&lt;p&gt;… at that time I wanted to change my order (wanted black screws set instead of regular ones). I contacted Hugo and he replied fast that he will take a look but he believes it will be possible. Maybe this was the reason why same day order went to “labeled” status.&lt;/p&gt;

&lt;p&gt;4-3-2019 (Monday) - order is in “shipped” status again&lt;/p&gt;

&lt;p&gt;8-3-2019 (Friday) - I received the package (Croatia)&lt;/p&gt;

&lt;p&gt;Orballo’s website claims 24h for shipping. I cannot confirm exact time 100% because I changed my order during this process, however it was not 24 hours for sure. On the other hand, it does seem fast enough. Although, at the end I didn’t receive black screws but regular ones so I wonder why the delay. I didn’t get any message on the status of that order.&lt;/p&gt;

&lt;p&gt;Package arrived in thin cardboard box and individual pieces wrapped in thin foam sheets. I’d say it is OK. Some styrofoam would be better but I nothing was damaged in transport.&lt;/p&gt;

&lt;p&gt;However, there were some scratches on the surface and some very minor bending. Some worse case examples you can see on the photos. Scratches don’t look like they were made in transport. It’s more like frame (or part of it) was assembled before. Or it was just not handled too carefully in production process. Personally, I don’t care. It’s steel frame, not underwear :D&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/uploads/scratches.jpg&quot; alt=&quot;Scratches and bents on Orballo steel frame&quot; title=&quot;Scratches and bents&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Anyway, all together, it is &lt;strong&gt;4/5&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;orballos-tatara-steel-frame-derivative-for-anet-a8---first-impressions&quot;&gt;Orballo’s Tatara steel frame derivative for Anet A8 - first impressions&lt;/h2&gt;

&lt;p&gt;First impression after unpacking everything was very positive. Small pack of Haribo candy was a nice touch. Something to chew on while assembling :) I also see Y belt tensioner metal parts are included, but also spool holder parts, nice. Only one minor error in production found but without any visual or functional effect. Closer inspection reveals that not all plates are exactly 3 mm thick. It varies from 2,80 to 3,00 mm. It might be polishing before powdering, or sheet tolerance, not sure. I don’t feel like it has much influence on stability of the frame.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/uploads/parts.jpg&quot; alt=&quot;Orballo Tatara steel frame for Anet A8 - parts&quot; title=&quot;what's in the box&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There’s also no assembly manual but who needs one right? I don’t see how people replacing frames of their DIY 3d printers are not also able to do it without manual. Also, there is enough Tatara stuff online and it should be the same.&lt;/p&gt;

&lt;p&gt;When I was asking a local cutter for a quote on cutting Tatara frame, I remember very well that tolerance on steel rod holes was H7. That is -0/+0.018 mm in diameter. This ensures tight fit of steel rods and no wobble. Tatara team suggests other methods of fixing steel rods if that tolerance cannot be met. And here comes my first real worry about Orballo’s Tatara frame, holes were 8,28 mm! I’m afraid steel rods will wobble. We will see.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/uploads/wobble.jpg&quot; alt=&quot;Smooth rods wobble&quot; title=&quot;Smooth rods holes too big&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Further comparing with original Tatara specs just made things worse, but this time I was to blame. Somehow, I expected Orballo is too small company to do much custom designing. When reading “Tatara derivative” I expected they changed a logo on it only. Oh I was wrong. Main frame is much narrower, left and right side for about 15mm, top one too. This however may also be an advantage. This way, there is more space left of the bed to squeeze in bed cable chain. Some steel frames make it hard to do.&lt;/p&gt;

&lt;p&gt;It’s still massive and made of steel and I cannot believe that makes much of a difference in rigidity but then I noticed bottom part was changed even more. Orballo removed rigid square frames below the bed, that extend outside and also provide a place to screw in rubber feet to avoid vibrations. Instead, they used much less rigid corner brackets. Knowing one of the biggest flaws of original Anet A8 frame was bending of front Y axes plate, I really disliked this solution. Also, those brackets do have some holes in them, that appear to be useful for screwing in rubber feet, but they’re located too much inside, 10cm away from outside boundaries of the printer and it doesn’t leave me much confidence in stability.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/uploads/bottom-frame.jpg&quot; alt=&quot;Tatara vs Orballo bottom frame&quot; title=&quot;Tatara vs Orballo bottom frame&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;* (Original tatara on the left. Images taken from Orballo and Tatara pages)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;More on exact changes from original Tatara frame later. For now, I can just stay that initial good impression turned to worry.&lt;/p&gt;

&lt;h2 id=&quot;assembly&quot;&gt;Assembly&lt;/h2&gt;

&lt;p&gt;It starts great, easy. No manual needed if you have seen a single photo of any similar frame. Holes fit perfectly and securely, and everything is tight and square. I managed to level top and bottom (front/back) plates to only +/- 0.01 degrees difference. Obviously, fact that some parts were a bit bent had no influence on rigidity of the frame.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/uploads/leveling.jpg&quot; alt=&quot;Frame squareness&quot; title=&quot;Frame squareness&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After first 5 main pieces of the frame are assembled, small issues arise. First, this is marketed as “drop in replacement” but it is not 100% true. Because original Anet A8 frame is 8mm thick and this frame is 3mm, original screws are too long. I anticipated this earlier, thanks to Orballo’s website which offered “related products” on their website when I was ordering the frame. It suggested Prusa i3 frame M3x12 screws package so I ordered that too for about 4€ extra. I’m sorry that only suggestion was regular metallic color and they also offer black ones. I discovered it manually too late to change order. New screws are also way better and they look better. It’s really not an option here, simply buy those too. I think it would be better if Orballo included it in the package with the frame actually. 4€ lower price cannot measure up with frustration of the clients when they try to use the old screws on this frame.&lt;/p&gt;

&lt;p&gt;More issues with screws appear. I don’t know how their steel Prusa i3 frame is, but those do not fit Anet frame completely. In example, I cannot screw down X axis motors. Plate is now 5mm thinner and holes in my steppers (original) are not that deep. I found some ugly ones around until I buy new ones. Similar thing, but only visually, is with LCD screen holder. You can screw it with those 12mm screws but 10mm ones would not stick out. Same happened to several less important places like PSU, electronics, etc. Nothing important but for visual puritans, it can be better.&lt;/p&gt;

&lt;p&gt;I must say that Tatara team left a big impression on me. They gained my trust with high quality design, rigidity measurements and other material that really explains and supports their design choices. Then they gave it away to Open Source and refuse to even earn money on selling cut frames. You ran into those small design details all the time. In example, Z axis motor plates. Tabs and holes are made in such a way that you can assemble them *after* you have already tightened main frame parts. Tabs are a bit narrower and holes wider, which enables you to slide parts in with no effort. On the other side, small things like that just confirm my suspicion how Orballo is not on that level. Bottom plates I mentioned earlier they replaced with some sort of corner brackets. Of course, those brackets do not have tabs so carefully designed and cannot be inserted later. I had to unscrew front and back plates to insert them. It appears I was wrong about assembly manual. You will be able to assemble frame without it, but you will have some setbacks and you have nowhere to look as this is not a original  Tatara frame. Some frustration piles up slowly.&lt;/p&gt;

&lt;p&gt;Next setback is Y axis motor plates. There are 2 identical plates here, similar to only single plate of original Tatara v1 (Tatara v1.1 changed the design here completely). And there are 3 assembly holes in the plate. Hm. Original Tatara suggests a single plate in the rightmost hole but supplied plates do not fit. If you use single plate in the middle hole, pinion is not centered with the belt. Only after watching Orballo i3 Prusa video I realized both plates go in 2 (left) holes, pinion is flipped and 9mm spacers are put between plates (not to mention that supplied screws don’t fit anymore because of this). Actually I like this approach more than Tatara’s. It’s way more rigid. Tatara also detected this and upgraded design for v1.1 but this works great too. However, you need longer screws, you need 9 mm spacers. You can print spacers (&lt;a href=&quot;https://www.thingiverse.com/thing:3029114&quot;&gt;here&lt;/a&gt;) but probably your printer is disassembled by now! I did research of what I need to print before, but there was no way I could have known about this. Luckily, I had some 9 mm aluminum spacer laying around from my CNC project.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/uploads/yaxismotor.jpg&quot; alt=&quot;Y axis motor brackets&quot; title=&quot;Y axis motor brackets&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;* (left: how I thought it should be, right: how it should be + some spacers I’m missing. And yes, supplied screws are too short here)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you don’t want to do that mistake yourself, here is the list of what you need to get printer in workable state and to avoid future disassembly:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.thingiverse.com/thing:3029114&quot;&gt;Y axis motor spacer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.thingiverse.com/thing:2358104&quot;&gt;Tatara for ANet A8 printable parts&lt;/a&gt; - what you need from here is Y axis belt tensioner, 2 x Belt Y bed fix, Belt Y bed fix top&lt;/li&gt;
  &lt;li&gt;not necessary but to avoid ruining your cables I suggest printing some square and round cable helpers from the link above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, what didn’t seem like a problem at the start slowly piles up frustration. It’s ok not to include manual but then stick to original Tatara design or include all the necessary parts and screws. This product just cries for correct screws and printable parts set (later ones at least in digital form).&lt;/p&gt;

&lt;p&gt;Another difference in design… Z axis stop switch is fixed to the frame using only one slot while original Anet and Tatara use two. Not a big deal since this is a steel frame so you can tighten the screw without worrying about plastic breaking. But still, I really don’t see why effort was taken to make things worse.&lt;/p&gt;

&lt;p&gt;Bed is exact copy of Tatara v1.1 (improved v1 version, tiny bit less rigid but 100 g lighter). Good. I used the opportunity to replace the Y axis belt with a better one (with fiberglass). Y axis bearings I replaced with IGUS ones long time ago.&lt;/p&gt;

&lt;p&gt;Electronics fit perfectly, holes are correct. LCD screen needs some more printed parts but it is good enough to test. All in all, not the best experience but it ends up with rock solid frame. It does not bend, sits on the flat surface perfectly with no movement at all. Only two things worry me at this stage: steel rods wobble because of too wide holes and front Y axis plate bending because of redesigned bottom frames. But we will see about this in print tests.&lt;/p&gt;

&lt;p&gt;Assembly: &lt;strong&gt;3.5/5&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;first-tests-and-impressions&quot;&gt;First tests and impressions&lt;/h2&gt;

&lt;p&gt;Very first impression after switching it on and leveling the bed was that printer is more quiet! It seems original Anet frame added to some noise and I wasn’t really expecting it. There is no visible movement of the frame while printing. It is much easier to (re)level bed now because with original frame, every movement of the printer, ever the slight one, made a big difference. Not anymore.&lt;/p&gt;

&lt;p&gt;About Y axis front plate bending…  until I tightened the belt I could move it about 0.5mm with my finger (compared to maybe 2 to 3 mm on the original Anet frame). It was still too much.  However, after tightening the belt, this is not visible to a naked eye anymore. However, I still hate this and prefer original tatara design.&lt;/p&gt;

&lt;p&gt;About steel rods wobbling: fixing bed and tightening the belt, also fixing Y axis decreased a wobble but it is still there. Some force is needed to make them move and I wonder if that amount of force is going to be reached when printing but I don’t like it. It’s just a matter of drilling 8 mm holes with better tolerance. I shouldn’t need to deal with this.&lt;/p&gt;

&lt;p&gt;May old Anet frame was skewed. I haven’t calibrated the printer yet but I had to delete skew compensation in Marlin. It appears to be perfect or only very slightly skewed.&lt;/p&gt;

&lt;p&gt;My LCD screen started to show garbage during print. It is static interference. Is it a metal frame or I wired LCD screen cable closer to other cables this time? You can find many solutions to this online, but I just wrapped some aluminum foil around it and it seems to works now.&lt;/p&gt;

&lt;p&gt;At this point, I replaced only the frame and Y axis belt. IGUS bearings on the bed were already there and everything else the same. I don’t have many old test prints but found 2 simple ones to make the comparison. Let’s see how those compare.&lt;/p&gt;

&lt;h2 id=&quot;before-and-after-prints&quot;&gt;“Before and after” prints&lt;/h2&gt;

&lt;p&gt;The main “enemy” of my old Anet frame was vibration, both on X and Y axes. I managed to make some almost perfect prints after carefully tightening belts, re-leveling bed, slowing down the speed, but it wouldn’t last long before ringing effect was visible. That was what I wanted to eliminate with this upgrade. Second goal for me was to increase the speed. Tatara frame should be able to take double acceleration and jerk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test 1 - 20mm hollow cube&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Speed 50mm/s (walls 25mm/s), 0.2 mm layer hight, 0.45 mm layer width, 0,4 mm nozzle, 205 C extrusion (Devil Design PLA)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Very simple test of flat surfaces but also the one that shows effects of vibrations. On the right side is old Anet print. At the bottom edge you can see lines caused by vibration. As I said, I could make them go away for short period of time but they would always come back.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/uploads/printtest1.jpg&quot; alt=&quot;Before and after print test nr.1&quot; title=&quot;Before and after print test nr.1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On the left side you can see a new print. Those lines are completely gone. For now, ignore diagonal lines on the new print. This is a problem unrelated to the frame (I’m suspecting the fan).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test 2 - Marvin figure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Speed 50mm/s (walls 25mm/s), 0.2 mm layer hight, 0.45 mm layer width, 0,4 mm nozzle, 205 C extrusion (Devil Design PLA)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/uploads/printtest2.jpg&quot; alt=&quot;Before and after print test nr.2&quot; title=&quot;Before and after print test nr.2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I printed maybe 50 Marvins on the old Anet with variable success. However, ringing behind his ears was present on each and every one of them. You can see it on the right. On the left, was my first try and it is not visible anymore. Again, ignore other flaws on the print, this test was targeted at ghosting/vibration and printer/filament is not calibrated well yet.&lt;/p&gt;

&lt;h2 id=&quot;so-how-good-is-orballo-printing-tatara-steel-frame-for-anet-a8&quot;&gt;So, how good is Orballo Printing Tatara steel frame for Anet A8?&lt;/h2&gt;

&lt;p&gt;Obviously, this is not a complete test. Still much to do (read on to TODO section). But it is enough to form some opinion of the product and Orballo service. After some printing I’d give &lt;strong&gt;4/5&lt;/strong&gt; for quality of assembled frame. This wobble in smooth rods worry me, I don’t like Orballo’s bottom frame compared to Tatara (no rubber feet and front plate not so rigid). In fact I already started designing upgrades to address some of those issues (&lt;a href=&quot;https://www.thingiverse.com/thing:3483630&quot;&gt;Z axis anti wobble&lt;/a&gt;). We will see how it goes and I’ll report.&lt;/p&gt;

&lt;p&gt;But point is, I didn’t want to deal with this. I wanted the best metal frame I can find. Tatara guys left an impression on me. It is a very professional and well thought product. But then comes Orballo’s “derivate” which I don’t understand. Don’t get me wrong, I have positive opinion about Orballo printing, I can definitely see some time and effort invested in their business. They design and sell their own custom full steel Prusa i3 printers, they have good prices and good service. Somebody there had to sit down and spend hours in CAD to change Tatara designs, test them, manufacture and sell. What I don’t understand is WHY? I don’t see any improvements (except maybe Y axis motor plate) so my only conclusion is that they wanted to cut down on costs. But then they include spool holder and black powdered finish which definitely is not cutting down costs. Was it worth 30€ (+ painting) difference for me or I was better off ordering original design from Ebay? Honestly I don’t know. I can tell it’s not the best frame out there, at least not in Europe. Best value for price? Most probably.&lt;/p&gt;

&lt;h2 id=&quot;all-together-from-website-to-print---the-verdict&quot;&gt;All together… from website to print - the verdict&lt;/h2&gt;

&lt;p&gt;I already wrote about Orballo service and after assembling the printer I think I need to come back to that. I like the company, their prices, website, selection of products, communication with Hugo. I will buy from them again. I also see some effort there to make their own products and to make them good and affordable. However I cannot shake off the feeling they’re just one step too short. Maybe understaffed? Sometimes I have a feeling it is a single person company. Just a few tiny steps are required… include manual or video, include correct screws with the frame, link to printable parts, take more care in documenting your in-house products online.  If you have to change excellent Tatara design, explain why! Explain what were your goals and compromises made, backup your decisions with facts and comparisons, stand behind it! Right now, I have many Tatara posts, user group, tests and documents where they explain their choices and stand behind it. Orballo has almost nothing to back up their design changes and then only authority of designer is waged. Tatara wins there easily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;relatively fast delivery&lt;/li&gt;
  &lt;li&gt;good communication with seller&lt;/li&gt;
  &lt;li&gt;huge improvement compared to original Anet A8 frame&lt;/li&gt;
  &lt;li&gt;black finish and spool holder included&lt;/li&gt;
  &lt;li&gt;good price&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;no manual (and you do need it!)&lt;/li&gt;
  &lt;li&gt;not as rigid as original Tatara design (main frame narrower, bottom frame dramatically changed, no proper place for rubber feet)&lt;/li&gt;
  &lt;li&gt;smooth rod holes wobble as are not made to Tatara tolerances (holes too wide)&lt;/li&gt;
  &lt;li&gt;screws set is extra and does not 100% fit  this model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recommend? &lt;strong&gt;YES&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Final rating: &lt;strong&gt;4/5&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;todo&quot;&gt;TODO&lt;/h2&gt;

&lt;p&gt;I’ll update this post and (hopefully) write new ones as I manage to do more tests and upgrades. On my list is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;properly calibrate printer and do more complex tests (speed tests too!)&lt;/li&gt;
  &lt;li&gt;design and upgrade parts to fix wobbly smooth rods&lt;/li&gt;
  &lt;li&gt;test if fixing leadscrews is a good thing to do or makes things worse&lt;/li&gt;
  &lt;li&gt;upgrade to Titan extruder (Bowden) with V6 hotend and BL touch sensor (all AliExpress copies)&lt;/li&gt;
  &lt;li&gt;upgrade board to RAMPS 1.4 + MEGA2560 + LCD 12864&lt;/li&gt;
  &lt;li&gt;upgrade to Octoprint using Raspberry Pi B&lt;/li&gt;
  &lt;li&gt;make alu extrusion/wood/plexiglass enclosure with smoke detector, external display and camera&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So… stay tuned for news and please comment/correct me if I made any errors!&lt;/p&gt;

&lt;hr /&gt;</content><author><name>Mirko Vukušić</name><uri>http://mirko.vukusic.net</uri></author><category term="Anet A8" /><category term="review" /><category term="Tatara" /><category term="Orballo" /><summary type="html">Review of buying process, assembly and results of Orballo's rework of Tatara steel frame for Anet A8.</summary></entry><entry><title type="html">Javascript next month (or previous month) with snapping to the first/last day of a month</title><link href="http://mirko.vukusic.net/programming/javascript-prev-next-month-with-snapping-to-first-last-day-of-month/" rel="alternate" type="text/html" title="Javascript next month (or previous month) with snapping to the first/last day of a month" /><published>2017-01-10T00:00:00+00:00</published><updated>2017-01-10T00:00:00+00:00</updated><id>http://mirko.vukusic.net/programming/javascript-prev-next-month-with-snapping-to-first-last-day-of-month</id><content type="html" xml:base="http://mirko.vukusic.net/programming/javascript-prev-next-month-with-snapping-to-first-last-day-of-month/">&lt;p&gt;I’m working on some hotel booking reports and needed flexible selection of date ranges. Solution was flexible, user could select any date range he/she wanted, but this came with the cost of bad UI usability/efficiency. Users do require to be able to select some very specific date ranges for their reports but often they just wanted to select a complete month and then browse easily to previous/next one. JavaScript calendar did the job, but required too many clicks to do it. Solution was obvious, add “month browsing buttons” to UI.&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#the-challenge&quot; id=&quot;markdown-toc-the-challenge&quot;&gt;The challenge&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-solution&quot; id=&quot;markdown-toc-the-solution&quot;&gt;The solution&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#dateprototypeislastdayofmonth&quot; id=&quot;markdown-toc-dateprototypeislastdayofmonth&quot;&gt;Date.prototype.isLastDayOfMonth()&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#dateprototypenextmonth&quot; id=&quot;markdown-toc-dateprototypenextmonth&quot;&gt;Date.prototype.nextMonth()&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#snapping-to-the-last-day-of-the-month&quot; id=&quot;markdown-toc-snapping-to-the-last-day-of-the-month&quot;&gt;“Snapping” to the last day of the month&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#dateprototypeprevmonth&quot; id=&quot;markdown-toc-dateprototypeprevmonth&quot;&gt;Date.prototype.prevMonth()&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-challenge&quot;&gt;The challenge&lt;/h2&gt;
&lt;p&gt;At first, adding +/- month buttons seemed trivial. I thought something like &lt;code class=&quot;highlighter-rouge&quot;&gt;date.setMonth(date.getMonth()+1)&lt;/code&gt; would do the job, but I was wrong. Not all months have equal number of days, it can be anywhere from 28 to 31. If you simply add a month to a month having more days than the next one - overflow happens:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;let date = new Date('2016-1-31')
date.setMonth(date.getMonth()+1)
console.log(date.toDateString())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Above code outputs &lt;code class=&quot;highlighter-rouge&quot;&gt;Wed Mar 02 2016&lt;/code&gt;. There is a logic to it, but for financial/booking reports it is not what you want.&lt;/p&gt;

&lt;p&gt;It would be easy to enable my users to browse only by complete month, but they sometimes need to browse by month, but not by &lt;strong&gt;complete&lt;/strong&gt; month. I.e. they want only first 10 days of months. So, clicking “next month” on the date range &lt;code class=&quot;highlighter-rouge&quot;&gt;2016-1-1 to 2016-1-10&lt;/code&gt; needs to bring them to &lt;code class=&quot;highlighter-rouge&quot;&gt;2016-2-1 to 2016-2-10&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I wanted to minimize number of UI elements and clicks so I needed an intelligent solution that will guess what user wants without adding more UI elements. Base of that logic was:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;if any of date range boundaries (&lt;code class=&quot;highlighter-rouge&quot;&gt;from&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;to&lt;/code&gt; date) is the first or the last day of the month - add/substract a month but make sure resulting boundary remains the first/last day of the month.&lt;/li&gt;
  &lt;li&gt;if a date range boundary is not the first or the last day of the month - keep it that way but still take care of the overflow&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;the-solution&quot;&gt;The solution&lt;/h2&gt;
&lt;p&gt;I decided to extend standard JavaScript &lt;code class=&quot;highlighter-rouge&quot;&gt;Date&lt;/code&gt;. This is &lt;strong&gt;NOT&lt;/strong&gt; a good practice (&lt;a href=&quot;http://stackoverflow.com/questions/14034180/why-is-extending-native-objects-a-bad-practice&quot;&gt;read this&lt;/a&gt;) but for this demonstration and my testing it was the simple thing to do.&lt;/p&gt;

&lt;h3 id=&quot;dateprototypeislastdayofmonth&quot;&gt;Date.prototype.isLastDayOfMonth()&lt;/h3&gt;
&lt;p&gt;First I needed a helper to be used in &lt;code class=&quot;highlighter-rouge&quot;&gt;nextMonth()&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;prevMonth()&lt;/code&gt; - any easy way to check if a date is the last day of the month:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Date.prototype.isLastDayOfMonth = function() {
	let check = new Date(this.getTime())
	check.setDate(check.getDate() + 1)
	return check.getDate() === 1
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Test is very simple. It adds a day to a date and checks if it became 1st in the month. Returns &lt;code class=&quot;highlighter-rouge&quot;&gt;true&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;dateprototypenextmonth&quot;&gt;Date.prototype.nextMonth()&lt;/h3&gt;
&lt;p&gt;Now to more difficult part:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Date.prototype.nextMonth = function() {
  let oldDate = new Date(this)
  let oldMonth = oldDate.getMonth()
  
  let newDate = new Date(oldDate)
  newDate.setMonth(oldMonth+1)
  let newMonth = newDate.getMonth()
  
  //prevent overflow
  if (newMonth-oldMonth &amp;gt; 1) {
    newDate.setDate(0)
  } else {
    //snap to last day of month (not needed if overflow was fixed)
    if (oldDate.isLastDayOfMonth()) {
      newDate = new Date(newDate.getFullYear(), newMonth + 1, 0)
    }
  }
  
  return newDate
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, original &lt;code class=&quot;highlighter-rouge&quot;&gt;date.setMonth(date.getMonth()+1)&lt;/code&gt; idea is here extended with another check  and that is overflow check. If adding a month resulted in month number difference larger than 1, we have an overflow!&lt;/p&gt;

&lt;p&gt;Note that I ignored turn of the year because then, month 12 (which is 11 in JavaScript because months are zero-based) turns to month 1 (which is 0) and that makes a difference of -11 which makes &lt;code class=&quot;highlighter-rouge&quot;&gt;newMonth-oldMonth &amp;gt; 1&lt;/code&gt; return false and overflow will not be fixed. Thats is what we want because it is never needed (December and January always have 31 days so overflow cannot happen).&lt;/p&gt;

&lt;p&gt;Overflow is simply fixed by &lt;code class=&quot;highlighter-rouge&quot;&gt;newDate.setDate(0)&lt;/code&gt;. Since dates are not zero-based in JavaScript (like months are), setting a date to 0 basically means 1 day before 1st in month, which is always the last day of previous month.&lt;/p&gt;

&lt;h3 id=&quot;snapping-to-the-last-day-of-the-month&quot;&gt;“Snapping” to the last day of the month&lt;/h3&gt;
&lt;p&gt;Another important check here is &lt;code class=&quot;highlighter-rouge&quot;&gt;oldDate.isLastDayOfMonth()&lt;/code&gt;. This is what makes it “intelligent”. This part  tries to guess what user wants. So if original date was the last day of the month, we will make sure that result also is. Because most probably user does not expect to select ‘2016-2-29’, click next and get ‘2016-3-29’. We can bet he wants ‘2016-3-31’. This line fixes overflow:  &lt;code class=&quot;highlighter-rouge&quot;&gt;newDate = new Date(newDate.getFullYear(), newMonth + 1, 0)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;There are two things we DON’T do here (for performance) and it might be important to notice them:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;When “snapping” to the last day of the month, we don’t check if date needs to be “snapped” at all. So, second part of check &lt;code class=&quot;highlighter-rouge&quot;&gt;oldDate.isLastDayOfMonth() &amp;amp;&amp;amp; newDate.isLastDayOfMonth()&lt;/code&gt; is not being done. This means we will “snap” &lt;code class=&quot;highlighter-rouge&quot;&gt;newDate&lt;/code&gt; even if it is already the last day of the month. That’s because &lt;code class=&quot;highlighter-rouge&quot;&gt;isLastDayOfMonth()&lt;/code&gt; is more expensive than snapping itself. At least it looks like that to me but I never tested this :)&lt;/li&gt;
  &lt;li&gt;We don’t do “snapping” if overflow was fixed before. That’s why I’ve put it in &lt;code class=&quot;highlighter-rouge&quot;&gt;else&lt;/code&gt; within overflow check. Fact is, overflow fix sets the date to the last day of the month so we can be sure it’s not needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can check finished &lt;code class=&quot;highlighter-rouge&quot;&gt;nextMonth()&lt;/code&gt; with quick demonstration:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cconsole.log('nextMonth')
console.log('2016-11-30 =&amp;gt; ' + new Date('2016-11-30').nextMonth().toDateString())
console.log('2016-12-31 =&amp;gt; ' + new Date('2016-12-31').nextMonth().toDateString())
console.log('2016-1-31 =&amp;gt; ' + new Date('2016-1-31').nextMonth().toDateString())
console.log('2016-2-29 =&amp;gt; ' + new Date('2016-2-29').nextMonth().toDateString())
console.log('2016-3-31 =&amp;gt; ' + new Date('2016-3-31').nextMonth().toDateString())
console.log('2016-4-30 =&amp;gt; ' + new Date('2016-4-30').nextMonth().toDateString())
console.log('----')
console.log('2016-4-29 =&amp;gt; ' + new Date('2016-4-29').nextMonth().toDateString())
console.log('2016-1-30 =&amp;gt; ' + new Date('2016-1-30').nextMonth().toDateString())
console.log('2016-2-15 =&amp;gt; ' + new Date('2016-2-15').nextMonth().toDateString())
console.log('----')
console.log('2016-1-1 =&amp;gt; ' + new Date('2016-1-1').nextMonth().toDateString())
console.log('2016-2-1 =&amp;gt; ' + new Date('2016-2-1').nextMonth().toDateString())
console.log('2016-3-1 =&amp;gt; ' + new Date('2016-3-1').nextMonth().toDateString())
console.log('2016-12-1 =&amp;gt; ' + new Date('2016-12-1').nextMonth().toDateString())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;dateprototypeprevmonth&quot;&gt;Date.prototype.prevMonth()&lt;/h3&gt;
&lt;p&gt;Going to previous month is very similar:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Date.prototype.prevMonth = function() {
  let oldDate = new Date(this)
  let oldMonth = oldDate.getMonth()
  
  let newDate = new Date(oldDate)
  newDate.setMonth(oldMonth-1)
  let newMonth = newDate.getMonth()
  
  //prevent overflow
  if (oldMonth-newMonth === 0) {
    newDate.setDate(0)
  } else {
    //snap to last day of month (not needed if overflow fixed)
    if (oldDate.isLastDayOfMonth()) {
      newDate = new Date(newDate.getFullYear(), newMonth + 1, 0)
    }
  }
  
  return newDate
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In fact, only a few details are changed: &lt;code class=&quot;highlighter-rouge&quot;&gt;setMonth&lt;/code&gt; goes -1 when initialising &lt;code class=&quot;highlighter-rouge&quot;&gt;newDate&lt;/code&gt; and overflow condition is a bit different (&lt;code class=&quot;highlighter-rouge&quot;&gt;if (oldMonth-newMonth === 0)&lt;/code&gt;). This means you can easily merge &lt;code class=&quot;highlighter-rouge&quot;&gt;prevMonth&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;nextMonth&lt;/code&gt; into single function if you want.&lt;/p&gt;

&lt;p&gt;Some code to demonstrate &lt;code class=&quot;highlighter-rouge&quot;&gt;prevMonth()&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;console.log('prevMonth')
console.log('2016-11-30 =&amp;gt; ' + new Date('2016-11-30').prevMonth().toDateString())
console.log('2016-12-31 =&amp;gt; ' + new Date('2016-12-31').prevMonth().toDateString())
console.log('2016-1-31 =&amp;gt; ' + new Date('2016-1-31').prevMonth().toDateString())
console.log('2016-2-29 =&amp;gt; ' + new Date('2016-2-29').prevMonth().toDateString())
console.log('2016-3-31 =&amp;gt; ' + new Date('2016-3-31').prevMonth().toDateString())
console.log('2016-4-30 =&amp;gt; ' + new Date('2016-4-30').prevMonth().toDateString())
console.log('----')
console.log('2016-4-29 =&amp;gt; ' + new Date('2016-4-29').prevMonth().toDateString())
console.log('2016-1-30 =&amp;gt; ' + new Date('2016-1-30').prevMonth().toDateString())
console.log('2016-2-15 =&amp;gt; ' + new Date('2016-2-15').prevMonth().toDateString())
console.log('----')
console.log('2016-1-1 =&amp;gt; ' + new Date('2016-1-1').prevMonth().toDateString())
console.log('2016-2-1 =&amp;gt; ' + new Date('2016-2-1').prevMonth().toDateString())
console.log('2016-3-1 =&amp;gt; ' + new Date('2016-3-1').prevMonth().toDateString())
console.log('2016-12-1 =&amp;gt; ' + new Date('2016-12-1').prevMonth().toDateString())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And that’s it! I’m always looking to make things smaller/faster so please let me know if you have some better methods! With no external libraries of course :)&lt;/p&gt;</content><author><name>Mirko Vukušić</name><uri>http://mirko.vukusic.net</uri></author><category term="JavaScript" /><summary type="html">Intelligent browsing by month (with snapping to the first/last day of a month, with no external library.</summary></entry><entry><title type="html">Automatic testing of web forms with CapserJS (PhantomJS)</title><link href="http://mirko.vukusic.net/programming/automatic-testing-of-online-forms-with-casperjs-phantomjs/" rel="alternate" type="text/html" title="Automatic testing of web forms with CapserJS (PhantomJS)" /><published>2017-01-05T00:00:00+00:00</published><updated>2017-01-05T00:00:00+00:00</updated><id>http://mirko.vukusic.net/programming/automatic-testing-of-online-forms-with-casperjs-phantomjs</id><content type="html" xml:base="http://mirko.vukusic.net/programming/automatic-testing-of-online-forms-with-casperjs-phantomjs/">&lt;p&gt;How many times have you realized your web forms stopped working some time ago? You lost your sales, maybe you’re spending some marketing budget to bring those customers to your website and all their inquiries ended up nowhere. You’re angry, and if it was for your client you have even more problems. Maybe it was a bug in your script, maybe server was down or maybe even everything was ok but mail was not delivered because mail server had an issue.&lt;/p&gt;

&lt;p&gt;I had this many times and once I reached hundreds of web contact forms for 20 websites of a single client, it was time to solve it. I decided to build a tool which will behave as a regular browser, go through each form, submit (possibly with different scenarios) and monitor email of the client to make sure email is delivered. Tool should be executed automatically over night and send errors (if any) in email in the morning.&lt;/p&gt;

&lt;p&gt;This is what I’ll cover in this post:&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#nodejs-an-casperjsphantomjs-to-the-rescue&quot; id=&quot;markdown-toc-nodejs-an-casperjsphantomjs-to-the-rescue&quot;&gt;Node.js an CasperJS/PhantomJS to the rescue&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#running-casperjs-in-nodejs&quot; id=&quot;markdown-toc-running-casperjs-in-nodejs&quot;&gt;Running CasperJS in Nodejs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#first-casperjs-test-script&quot; id=&quot;markdown-toc-first-casperjs-test-script&quot;&gt;First CasperJS test script&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#running-casperjs-tests-from-nodejs&quot; id=&quot;markdown-toc-running-casperjs-tests-from-nodejs&quot;&gt;Running CasperJS tests from Nodejs.&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#making-it-dynamic-and-configurable-using-mongodb&quot; id=&quot;markdown-toc-making-it-dynamic-and-configurable-using-mongodb&quot;&gt;Making it dynamic (and configurable) using MongoDB&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#last-but-not-the-least---is-email-delivered-to-inbox&quot; id=&quot;markdown-toc-last-but-not-the-least---is-email-delivered-to-inbox&quot;&gt;Last but not the least - is email delivered to inbox?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#wrapping-it-up&quot; id=&quot;markdown-toc-wrapping-it-up&quot;&gt;Wrapping it up&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#hosting&quot; id=&quot;markdown-toc-hosting&quot;&gt;Hosting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;nodejs-an-casperjsphantomjs-to-the-rescue&quot;&gt;Node.js an CasperJS/PhantomJS to the rescue&lt;/h2&gt;
&lt;p&gt;I checked &lt;a href=&quot;http://www.seleniumhq.org/&quot;&gt;Selenium&lt;/a&gt; browser automation but wanted something simple which works through Nodejs. That’s how I found &lt;a href=&quot;http://phantomjs.org/&quot;&gt;PhantomJS&lt;/a&gt; which is “headless WebKit scriptable with a JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.”. That’s what I was looking for, a browser I can control with JavaScript to execute my test scripts.&lt;/p&gt;

&lt;p&gt;To make it simpler, a bit more abstracted, &lt;a href=&quot;http://casperjs.org/&quot;&gt;CasperJS&lt;/a&gt; was the next tool: “CasperJS allows you to build full navigation scenarios using high-level functions and a straight forward interface to accomplish all sizes of tasks”.
 Another bonus of CasperJS is that besides PhantomJS (which is WebKit) it also supports &lt;a href=&quot;https://slimerjs.org/&quot;&gt;SlimerJS&lt;/a&gt; which runs on top of Gecko, the browser engine of Mozzila Firefox. This enabled me to test different browsers using same test scripts.&lt;/p&gt;

&lt;h2 id=&quot;running-casperjs-in-nodejs&quot;&gt;Running CasperJS in Nodejs&lt;/h2&gt;
&lt;p&gt;This is not as easy as it might seem at start. WebKit and Node don’t run in the same environment so communication between the two is not trivial. I checked &lt;a href=&quot;https://github.com/SpookyJS/SpookyJS&quot;&gt;SpookyJS&lt;/a&gt; which makes it easier to drive CasperJS from Nodejs but at the end decided not to use it because running CasperJS as a subprocess call from Node (&lt;code class=&quot;highlighter-rouge&quot;&gt;exec()&lt;/code&gt;) was simple enough for what I need.&lt;/p&gt;

&lt;p&gt;So, at the end my &lt;code class=&quot;highlighter-rouge&quot;&gt;package.json&lt;/code&gt; dependencies section was looking like this:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;dependencies&quot;: {
    &quot;basic-auth-connect&quot;: &quot;^1.0.0&quot;,
    &quot;body-parser&quot;: &quot;^1.15.2&quot;,
    &quot;casperjs&quot;: &quot;^1.1.3&quot;,
    &quot;express&quot;: &quot;^4.14.0&quot;,
    &quot;express-handlebars&quot;: &quot;^3.0.0&quot;,
    &quot;mongodb&quot;: &quot;^2.2.12&quot;,
    &quot;phantomjs-prebuilt&quot;: &quot;^2.1.13&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It also has some other stuff like mongodb, express, etc. That’s for later.&lt;/p&gt;

&lt;h2 id=&quot;first-casperjs-test-script&quot;&gt;First CasperJS test script&lt;/h2&gt;
&lt;p&gt;Since I wanted many test scenarios to be executed, I decide to put them in &lt;code class=&quot;highlighter-rouge&quot;&gt;./tests/&lt;/code&gt; folder of my project. CasperJS comes with CLI so &lt;code class=&quot;highlighter-rouge&quot;&gt;casperjs &amp;lt;testfile.js&amp;gt;&lt;/code&gt; will run your test. This makes it easy to test your script before even wrapping Nodejs around it. My first test scenario was fairly simple, what I want to do is:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;open contact form page&lt;/li&gt;
  &lt;li&gt;fill in the form with test data&lt;/li&gt;
  &lt;li&gt;click submit button&lt;/li&gt;
  &lt;li&gt;make sure resulting page reports success&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For this, CasperJS test script looks similar to:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// open form page
casper.start(&quot;http://myweb.com/formurl&quot;, function(response) {
    if (response === undefined || response.status === null || response.status &amp;gt;= 400) {
        this.die('ERROR loading initial url')
    }
})

// wait for form to be loaded and fill it
casper.waitForSelector(
    'form.contactform', // CSS3 (or Xpath!) selector to find form on the page
    function() {
        try {
            // pass an object with filed names and values to fill
            this.fill('form.contactform', {name: &quot;Formtester&quot;, email: &quot;fomtester.myweb.com&quot;}, false)
        } catch (e) {
            this.die('ERROR filling form fields')
        }
    },
    function() {
        this.die('TIMEOUT waiting for form to load')
    }
)

// find and click submit button
casper.then(function() {
    if (!casper.exists(&quot;input.submit&quot;)) { // CSS3 (or Xpath!) selector to find submit button on the page
        this.die('ERROR, no submit button found.')
    }
    this.click(&quot;input.submit&quot;)
})

// wait for thankyou page
casper.waitForUrl(
    new RegExp(&quot;thankyou.html&quot;),
    function() {},
    function() {
        console.log('current url:', this.getCurrentUrl())
        this.capture('screenshot.png') //you can even capture screentshot of the errored page!
        this.die('TIMEOUT waiting for thankyou page')
    }
)

// check if thank you page reported succes
casper.then(function() {
    const pageText = this.evaluate(function() {
        return document.body.innerText
    })
    if (!new RegExp(&quot;thank you&quot;).test(pageText)) {
        this.die('ERROR submitting form. Unexpected thankyou page message')
    }
})

// return OK if all passed and exit
casper.run(function() {
    this.echo('OK').exit()
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Script is commented so should be pretty self-explanatory. It also reveals some cool stuff you can do with CasperJS and PhantomJS, like &lt;code class=&quot;highlighter-rouge&quot;&gt;capture('screenshot.png')&lt;/code&gt; which enables you to capture a screenshot of the page (or only part of it!). Basically, complete DOM is accessible, together with events you can monitor or emit (like mouse clicks). It’s obvious you can do much more with this setup than simple form testing!&lt;/p&gt;

&lt;h2 id=&quot;running-casperjs-tests-from-nodejs&quot;&gt;Running CasperJS tests from Nodejs.&lt;/h2&gt;
&lt;p&gt;As mentioned earlier, I decided to use Nodejs subprocess. There are other options but this one was simple enough for what I needed. It’s just a matter of doing an &lt;code class=&quot;highlighter-rouge&quot;&gt;exec()&lt;/code&gt; call to &lt;code class=&quot;highlighter-rouge&quot;&gt;casperjs&lt;/code&gt;. I’ve created a &lt;code class=&quot;highlighter-rouge&quot;&gt;testRunner.js&lt;/code&gt; Nodejs module to make it reusable for more complex scenarios later. Core of this will be something like:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;console.log('executing casper test')
exec('casperjs ./tests/myFirsttest.js',
    { env: { 'PATH': '/usr/bin:./node_modules/.bin/:/app/.heroku/node/bin' } },
    function(err, stdout, stderr) {
        if (err || stdout.trim() !== 'OK') {
            console.log('ERROR. stdout was:' + stdout)
        } else {
            console.log('success')
        }
    }
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Please note that I’m also passing &lt;code class=&quot;highlighter-rouge&quot;&gt;env&lt;/code&gt; to &lt;code class=&quot;highlighter-rouge&quot;&gt;exec()&lt;/code&gt;. This makes sure Nodejs finds CasperJS and PhantomJS executables, in my local environment but also in future Heroku environment where I can run this for free because it’s really simple app which runs only once per day, spending almost no resources.&lt;/p&gt;

&lt;p&gt;Another thing to note here is that I’m reading &lt;code class=&quot;highlighter-rouge&quot;&gt;stdout&lt;/code&gt; from &lt;code class=&quot;highlighter-rouge&quot;&gt;casperjs&lt;/code&gt; at the end of execution. It is simple enough for what I need, but you could also subscribe to stdout/stderr of Nodejs subprocess and make CasperJS more verbose. This way you can achieve more complex scenarios.&lt;/p&gt;

&lt;h2 id=&quot;making-it-dynamic-and-configurable-using-mongodb&quot;&gt;Making it dynamic (and configurable) using MongoDB&lt;/h2&gt;
&lt;p&gt;Well, it’s not much of a work to execute single scenario for a single form, but I needed several scenarios for many forms. I’m not going to write all boilerplate here (will share complete project to GitHub soon), but I’ll mention most important parts in next steps on my list:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. extract all scenario-specific data&lt;/strong&gt; (like form url, form selector, form filed names with values, etc.) from first CasperJS script into JSON object.
Example of the result:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    &quot;type&quot;: &quot;myFirstTest.js&quot;,
    &quot;url&quot; : &quot;http://myweb.com&quot;,
    &quot;formSelector&quot; : &quot;form.contactform&quot;,
    &quot;formFill&quot; : {
        &quot;name&quot; : &quot;FormTester TEST&quot;,
        &quot;phone&quot; : &quot;123456789&quot;,
        &quot;emailaddress&quot; : &quot;formtester@myweb.com&quot;,
        &quot;comment&quot; : &quot;TEST TEST&quot;
    },
    &quot;submitButtonSelector&quot; : &quot;input.submit&quot;,
    &quot;thankyouPageUrlRegex&quot; : &quot;/thankyou.html$&quot;,
    &quot;thankyouPageTextRegex&quot; : &quot;Thank you&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. refactor test script&lt;/strong&gt; to receive this JSON as input (to reconfigure test accordingly). This included passing some more options to Nodejs &lt;code class=&quot;highlighter-rouge&quot;&gt;exec()&lt;/code&gt; in my &lt;code class=&quot;highlighter-rouge&quot;&gt;testRunner.js&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;exec(`casperjs ./tests/${test.type}.js --options='${encodeURIComponent(JSON.stringify(test.testOptions))}'`...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note &lt;code class=&quot;highlighter-rouge&quot;&gt;encodeURIComponent()&lt;/code&gt; usage which is necessary if you have weird characters in your config (quite usual for multi-language sites).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. store test(s) JSON to MongoDB&lt;/strong&gt; for easy management of tests (and later test execution logs).
Here, I added some more properties for each test. In example &lt;code class=&quot;highlighter-rouge&quot;&gt;_id&lt;/code&gt; field which is MongoDB’s unique identifier. Nice thing about it is that it contains a timestamp too which I’ll use later to know when each test is executed, so I get both timestamp and unique identifier in the same field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. make &lt;code class=&quot;highlighter-rouge&quot;&gt;runAllActiveTests()&lt;/code&gt;&lt;/strong&gt; Nodejs script to go through each test in MongoDB and execute it through &lt;code class=&quot;highlighter-rouge&quot;&gt;testRunner&lt;/code&gt;.
Apart from simple looping through MongoDB query result of all active tests, important thing to do here was to implement multiple scenarios. So note &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;type&quot;: &quot;myFirstTest&quot;&lt;/code&gt; in JSON above. Each &lt;code class=&quot;highlighter-rouge&quot;&gt;type&lt;/code&gt; of test (or scenario) will have a corresponding CasperJS script in &lt;code class=&quot;highlighter-rouge&quot;&gt;./tests/&lt;/code&gt; folder. This way, we can reuse same scripts for many tests/websites (changing only test config in DB) but can also make variations. In practice this is often the case with multi-language sites where single scenario (CasperJS test) works easily on different languages, but different sites (or completely different things we want to test on the same site) require some different steps in the test script.
Also, here we log execution status of each test to MongoDB, we will need it later for reporting.&lt;/p&gt;

&lt;h2 id=&quot;last-but-not-the-least---is-email-delivered-to-inbox&quot;&gt;Last but not the least - is email delivered to inbox?&lt;/h2&gt;
&lt;p&gt;It is very important to know if email is delivered. Often, website reports success but for many possible reasons email is not delivered. What I did is a simple API to Nodejs app which receives TestID, searches for it in DB and marks it’s status as “email received” or “success”. Now all we need to do is somehow instruct mail server to trigger that API call when email is received.&lt;/p&gt;

&lt;p&gt;I simply love Google Apps and this is one of the reasons. It’s very simple to do it using Google Apps Script. Even if you/your client don’t use Gmail, you can simply tell their mail administrator to forward emails from formtester@myweb.com to your Gmail inbox of choice and deal with mail there. To make filtering on mail server easier, and to pass Test ID to mail server, we will append string &lt;code class=&quot;highlighter-rouge&quot;&gt;[FormTester:&amp;lt;TestID&amp;gt;]&lt;/code&gt; to any of the form fields during execution of CasperJS test (i.e. “comment” field). This text should be included in email sent by the form, thus visible to mail server to process it. Simple! And does not require any changes to the code in form submitting procedures of the website (a very important requirement)!&lt;/p&gt;

&lt;p&gt;So, after you configured email forward to Gmail formtester account, remaining steps to make it work are:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Create a label &lt;code class=&quot;highlighter-rouge&quot;&gt;formTester&lt;/code&gt; in Gmail which will store form emails.&lt;/li&gt;
  &lt;li&gt;In Gmail, create filter for incoming mail that will move all mails having string &lt;code class=&quot;highlighter-rouge&quot;&gt;[FormTester:&lt;/code&gt; into folder/label above.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Create GApps script which will go through all mails labeled &lt;code class=&quot;highlighter-rouge&quot;&gt;formTester&lt;/code&gt;, extract &lt;code class=&quot;highlighter-rouge&quot;&gt;FormID&lt;/code&gt; from each and call our API with it.
Part of GApps script to call API:&lt;/p&gt;

    &lt;p&gt;/**&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;call FormTester web API and trigger confirmation action with passed testId
 */
 function triggerApiConfirmation(testId) {
var url = apiUrl + ‘/confirm-email-receive?id=’ + testId;
var response = UrlFetchApp.fetch(url, {“method”: “GET”, “headers”: headers, “muteHttpExceptions”: true});
if (response == ‘OK’) {
  Logger.log(‘triggered API received confirmation with success, got: ‘ + response);
} else {
  Logger.log(‘triggered API received confirmation but got error: ‘ + response);
}
return response;
 }&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And loop to call it on each mail with &lt;code class=&quot;highlighter-rouge&quot;&gt;formTester&lt;/code&gt; label:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// GLOBAL SETTINGS (for all scripts)

var inboxLabel = GmailApp.getUserLabelByName('[FormTester]');  //label where formtester email comes to
var apiUrl = 'http://mywebapi.com'; //API root URL
var adminEmail = 'me@myweb.com';  //where to send system errors
var selfEmail = 'formtester@myweb.com'; //own email (where we send failed email)
var headers = {
  &quot;Authorization&quot; : &quot;Basic &quot; + Utilities.base64Encode('apilogin:apipass')
};

/**
* go through FormTester label threads and detect emails with testId
*/
function monitorEmails() {
  var threads = inboxLabel.getThreads();
  //loop through threads
  for (var t = 0; t &amp;lt; threads.length; t++) {
    var emails = threads[t].getMessages();
    //loop through messages
    for (var i = 0; i &amp;lt; emails.length; i++) {
      var email = emails[i];
      if (email.isInTrash()) continue;
      var regexResult = email.getPlainBody().match(/\[FormTester:([0-9a-f]{24})\]/);
      if (regexResult &amp;amp;&amp;amp; regexResult[1]) {
        var testId = regexResult[1];
        //trigger API call if TestId detected
        Logger.log('FormTesterEmailRecognized containing ID=' + testId);
        var response=triggerApiConfirmation(testId);
        if (response == 'OK') {
          email.moveToTrash();
        } else {
          email.forward(selfEmail, {&quot;subject&quot;: &quot;[FormTesterError:&quot;+testId+&quot;] - response:&quot;+response});
          email.moveToTrash();
        }
      } else {
        email.forward(selfEmail, {&quot;subject&quot;: &quot;[FormTesterError:noIdFound]&quot;});
        email.moveToTrash();
        Logger.log('email detected but TestId not found in it! Forwarding email to self.');
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping it up&lt;/h2&gt;
&lt;p&gt;Again, no boilerplate in this article. GitHub project will soon be available where you’ll be able to see the complete code. Here is just a simple list of tasks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create 2 more scripts in GApps (and corresponding API calls in our Nodejs app): 1) script to start execution of all active tests, 2) script to get statuses of all tests and email reports&lt;/li&gt;
  &lt;li&gt;Put all GApps scripts into scheduler so they’re executed over night, in this order: 1) execute all tests, 2) execute email monitor/parser, 3) get statuses and email report&lt;/li&gt;
  &lt;li&gt;Build some frontend to manually see test statuses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example of mine:
 &lt;img src=&quot;/assets/img/formtester1.jpg&quot; alt=&quot;Form tester fronted screenshot&quot; title=&quot;Form tester fronted screenshot&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;hosting&quot;&gt;Hosting&lt;/h2&gt;
&lt;p&gt;As mentioned above I used &lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt; to host this project. Since  service is mostly needed only couple of times over night you will spend almost none Dyno hours there which makes it a free hosting for smaller projects. It’s easy to upgrade later for more serious tests. Of course like usual with Heroku, server management is none and deployment is a breeze, just link GitHub branch with Heroku and whatever you push there gets published. Or you can use one of many other equally simple deployment methods.&lt;/p&gt;

&lt;p&gt;Feel free to leave me a comment or suggestion.&lt;/p&gt;</content><author><name>Mirko Vukušić</name><uri>http://mirko.vukusic.net</uri></author><category term="JavaScript" /><category term="Nodejs" /><category term="CasperJS" /><category term="GApps" /><summary type="html">How many times your web forms stopped working and you realized it days later? This is how I stopped it from happening using CasperJS/PhantomJS and Node.</summary></entry></feed>