Web

A WALKTHROUGH

@nikjohn

WHAT'S THE POINT?

  • WEB TECHNOLOGY STACK
  • WEB DEVELOPMENT PROCESS
  • WEB TEAM
  • Q & A 

CONTENTS

  • SIZES
  • PAYLOADS
  • ARCHITECTURE
  • FRAMEWORKS
  • TESTING
  • TRANSLATION
  • DEPLOYMENT

SIZES

  • Adaptive application - pages are served by device specs.
  • WURFL - a 3rd party User Agent-based device detection service

4 SIZES

  • txt
  • xs
  • sm
  • lg

txt

width < 128px
OR
(ua.indexOf('/midp') > -1)

xs

Wireless Device
AND
(Proxied Browser
OR
width < 320px
OR
Pointing method != touch)

sm

Wireless Device
AND
!XS

lg

!Wireless Device
AND
!Legacy (e.g. IE8)
Sizes Resolution Wireless
Device
Special Condition

txt
 

< 128 px

Yes
   - ua contains          
‘midp’                 

xs
 

< 320 px

Yes
  - Non-touch device
-  Proxy browser  

sm
 

Yes

- Not ‘xs’                

lg
 

No
   
- Not legacy (e.g. IE8)

PAYLOAD

 

  • With the exception of some interactive content, everything is rendered server-side.
  • txt has, unlike other platforms, very few exceptions and mostly gets whatever is not exclusive to any other size

Markup/Styling

  • XS/SM/LG share a lot of markup and styling, SM/XS intersects the most.
  • Utilities exist to deliver markup/styling to each platform in a structured manner.
mixin character-count(max)
    if LG || SM
        p.char-counter(data-max-chars=max)
            span.current
                | 0
            span.divider
                |/
            span.max
                = max
xs
sm
lg
.only-xs()
.only-sm()
.only-lg()
.until-lg()
.from-sm()

STYLES

.item-detail.is-job {
  .item-intro {
    text-align: left;
  }

  .until-lg({
    .item-intro {
      padding-bottom: 1em;
      border-bottom: solid 1px @color-linnen-grey;
    }
  });
}

STYLES

  • Most pages have a Backbone view, which is optional
     
  • Handles all post-render DOM interaction for Javascript capable clients.

JAVASCRIPT

WHAT IS SENT ON THE WIRE?

ARCHITECTURE

SIMPLIFIED

BROWSER

MIDDLEWARE (NODE JS + EXPRESS JS)

API

http(s)

http(s)

BROWSER

MIDDLEWARE (NODE JS + EXPRESS JS)

API

http://bikroy.com/EN/ads/ads-in-bangladesh
GET http://api.bikroy.com/v1/serp
GET http://bikroy.com/EN/ads/ads-in-bangladesh

WHY A MIDDLEWARE?

  • SEO *

  • FEATURE PHONES

*Google supports crawling of JS-based applications, but recommends progressive enhancement still.

FRAMEWORKS

&

LIBRARIES

txt
xs
sm
lg

NODE JS (4.x)

NODE JS (4.x)

EXPRESS JS (4.x) 

BACKBONE JS MODELS

PUG (JADE) TEMPLATES 

BACKBONE VIEWS *

ZEPTO JS

* Admin uses a slimmer PoC as well - Furiosa

* No dependency in any direction between Backbone views and models

SERVER

CLIENT

TESTING

  • Unit testing for server libs, middlewares and helpers
  • Functional testing for anything that is part of the UI
  • Visual regression testing during CI builds to check UI integrity

Unit testing

  • Minimal unit testing is performed on independent components
  • Primary tool is mocha
var helpers = require('../../helpers/unit'),
	images = require(helpers.base + 'server/middleware/images');

suite('server/middleware/images', function() {
	test('images sm, jpg', function(done) {
		var request = {
			},
			response = {
				locals: {
					platform: 'lg'
				}
			};

		images(request, response, function() {
			assert.equal(response.locals.sizes.full.length, 2);
			assert.ok(response.locals.sizes.full[0].indexOf('.jpg') > -1,
				'image size has .jpg format');
			done();
		});
	});

	test('images sm, webp', function(done) {
		var request = {
				headers: {
					accept: 'text/html;q=0.9,image/webp,*/*;q=0.8'
				}
			},
			response = {
				locals: {
					platform: 'sm'
				}
			};

		images(request, response, function() {
			assert.equal(response.locals.sizes['list-normal'].length, 2);
			assert.ok(response.locals.sizes['list-normal'][0].indexOf('.webp') > -1,
				'image size has .webp format');
			done();
		});
	});
});
  • Performed with Selenium/Phantom.js, Nightwatch.js, Saucelabs (optionally), and a bunch of scaffolding of our own in the web-tester project.
     
  • Represents the clear majority of testing of the web application. Runs for all flows (not all permutations of all flows).

FUNCTIONAL TESTING

  • Uses mock end points generated by the api-functional-mock project
     
  • Can be run on Selenium or PhantomJS (Headless)

FUNCTIONAL TESTING

  • Loads a page for a certain market and locale, then runs commands to manipulate and assert application state. (~4k assertions in entire suite)

FUNCTIONAL TESTING

var helpers = require('../../helpers/functional');

module.exports = helpers.runAllSites({
	landing: function(market, locale) {
		var test = function(client) {
			var locales = helpers.markets[market].locales,
				countrySlug = helpers.markets[market].countrySlug,
				countryName = helpers.t('site.country', market, locale),
				expected;

			client.start('', market, locale)
				.shootAll(2)
				.assert.gtm({
					platform: 'lg'
				});

			if (locales.length > 1) {
				if (market === 'bikroy') {
					expected = (locale === 'en' ? 'bn' : 'en');
				}
				else if (market === 'ikman') {
					expected = (locale === 'en' ? 'si' : 'en');
				}

				client
					.assert.elementPresent('.ui-nav .locales')
					.assert.elementPresent('.locales li:nth-child(5n+' + (locales.length - 1) + ')')
					.getAttribute('.locales li:nth-child(5n+1) a', 'href', function(result) {
						this.assert.ok(result.value.indexOf('/' + expected) > -1,
							'Locale link URL');
					});
			}
			else {
				client.assert.elementNotPresent('.ui-nav .locales');
			}

			if (locale === helpers.markets[market].localeDefault) {
				client
					.assert.attributeMatches('link[rel="canonical"]', 'href',
						new RegExp('http:\/\/' + market + '\\.test(:\\d+)?\/' +
							locale))
					.assert.attributeMatches('.ui-logo a', 'href', '/$');
			}
			else {
				client
					.assert.attributeMatches('link[rel="canonical"]', 'href',
						new RegExp('http:\/\/' + market + '\\.test(:\\d+)?\/' +
							locale))
					.assert.attributeMatches('.ui-logo a', 'href', '/' + locale + '$');
			}

			client
				.assert.elementPresent('link[rel="prefetch"]')
				.assert.elementPresent('link[rel="prerender"]')
				.assert.translation('.home-top h1', 'home.top.title',
					market, locale, {
						country: countryName
					})
				.assert.elementsCount('.home-locations .home-group', 2)
				.assert.elementsCount('.home-locations .home-group.is-city ul', 2)
				.assert.elementsCount('.home-locations .home-group.is-region ul', 1)
				.assert.elementPresent('.home-focus')
				.assert.elementPresent('.home-safety')
				.assert.elementPresent('.home-fb')
				.assert.elementPresent('.home-categories')
				.assert.elementsCount('.home-categories .col-12', 7)
				.assert.attributeMatches('.home-categories .col-12:first-child a', 'href',
					'/ads/food-agriculture-in-' + countrySlug + '-47')
				.assert.containsText('.home-categories .col-12:first-child .count', '54,234');

			if (helpers.markets[market].news) {
				client
					.assert.elementPresent('.home-news')
					.assert.elementsCount('.home-news li', 2);
			}

			client.end();
		};
		test.apiMock = 'web/home/landing';
		return test;
	}
});

TO BE CONTINUED..

Saltside Web Stack

By Nikhil John

Saltside Web Stack

  • 728