{"id":20863340,"url":"https://github.com/howardabrams/labrats","last_synced_at":"2025-09-02T11:09:29.417Z","repository":{"id":7854902,"uuid":"9226926","full_name":"howardabrams/labrats","owner":"howardabrams","description":"A client-side multivariate testing (A/B tests) plugin for jQuery.","archived":false,"fork":false,"pushed_at":"2013-06-13T04:17:45.000Z","size":354,"stargazers_count":6,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-18T07:36:18.922Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/howardabrams.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-04-04T20:07:40.000Z","updated_at":"2025-07-03T21:47:57.000Z","dependencies_parsed_at":"2022-09-24T05:01:31.956Z","dependency_job_id":null,"html_url":"https://github.com/howardabrams/labrats","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/howardabrams/labrats","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howardabrams%2Flabrats","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howardabrams%2Flabrats/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howardabrams%2Flabrats/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howardabrams%2Flabrats/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/howardabrams","download_url":"https://codeload.github.com/howardabrams/labrats/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howardabrams%2Flabrats/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273272647,"owners_count":25076004,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-02T02:00:09.530Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-18T05:28:36.926Z","updated_at":"2025-09-02T11:09:29.401Z","avatar_url":"https://github.com/howardabrams.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"This `lab-rats` project provides a [jQuery plugin][1] for doing\n*multi-variate testing* ([A/B Tests][2]) on the client's browser...\nin other words, treating your customers like lab rats in order to\nengineer the best web application.\n\nQuick Start\n----------\n\nTo give an example of how to use this plugin, let's pretend you want\nto measure the results of changing the look of the *sign up button*.\nThis experiment will split your visitors into two equal\n\"groups\". Group 0 will be shown the Shiny Red button, and Group 1 will\nbe shown the Flashy Blue button. We call this test, the **Big Button**\nexperiment.\n\nTaking advantage of the `labrats` plugin is a simple two step process:\n\n### Step 1. Create a Function for each Group\n\nEach group will have a special function that will change or render the\npage slightly differently:\n\n    function shinyRed() {\n        $('#big-button').addClass('shiny-red');\n    }\n\n    function flashyBlue() {\n        $('#big-button').addClass('flashy-blue');\n    }\n\nNormally, you would put an element to report which element is shown to\nwhich user, but we'll get to that in a minute.\n\n### Step 2. Call the `labrats()` for Test Subject\n\nThe final step amounts to having the `labrats()` function call one of the\ncallback functions for the user based on their group.\n\n    $.labrats( { name: 'Big Button',\n                 callbacks: [ shinyRed, flashyBlue ] } );\n\nThat is all that is needed to get a barebones test showing different\nbutton styles to different users.\n\nOn Reporting\n---------\n\nOf course, a multivariate test is not really an experiement without\nthe scientific principles of observations and reporting.\n\nWhile I really can't help you in this regard, let me assume that\nyou've written a `tracking()` function that can send a message to\nsomething like [Google Analytics][3].\n\nNow, we just need to change our functions a wee bit:\n\n    function shinyRed() {\n        tracking('shown', 'shiny-red', guid);\n        $('#big-button').addClass('shiny-red').click( function(){\n            tracking('clicked', 'shiny-red', guid);\n        });\n    }\n\n    function flashyBlue() {\n        tracking('shown', 'flashy-blue', guid);\n        $('#big-button').addClass('flashy-blue').click( function(){\n            tracking('clicked', 'flashy-blue', guid);\n        });\n    }\n\nNotice each function tracks if the button was shown *and* whether it\nwas clicked. You really should track both to get a clear coorelation\nbetween the size of your test and its success.\n\nOn Identifying Users\n-------------\n\nThe plugin keeps track of a user account by storing a unique ID in the\nbrowser's stash of cookies, however, you *can* specify the ID you want\nit to use. For instance, assuming that you had a `guid` variable like:\n\n    var guid = 'bcfb3529-0fed-4b05-8414-db3e1d2b11da';\n\nYou can pass in this value as a `key` to the `$.labrats()` function:\n\n    $.labrats( { key: guid, name: 'Big Button',\n                 callbacks: [ shinyRed, flashyBlue ] } );\n\n**Note:** If a `key` is not specified, the user ID calculated is simply a\nlarge random number. It is NOT a GUID and may not be unique among all\nyour users. Since it is internal to this plugin and meaningless, it is\nobviously not very  useful for tracking and reporting results. This is\nwhy we recommend specifying your own ID key.\n\nOn Hashing Issues\n-----------------\n\nThe hashing algorithm that comes with this plugin is pretty... uh,\nsimplistic. Actually, it is downright stupid, and the resulting\ndistribution isn't great. However, you can specify a hashing\nalgorithm.\n\nThe function you give the `hash` must be able to accept a string\nand return an integer number, for instance:\n\n      $.labrats.configure( {\n           hash: function(key) {\n                     return murmurhash3_32_gc(key, 73);\n                 }\n      });\n\nIt seems the [MurmurHash][4] is quite good at distribution, and\n[initial experiments][5] show it a good algorithm for this plugin.\n\n  [1]: http://www.jquery.com\n  [2]: http://en.wikipedia.org/wiki/A/B_testing\n  [3]: https://developers.google.com/analytics/\n  [4]: http://en.wikipedia.org/wiki/MurmurHash\n  [5]: dispersion/dispersion.html\n\nRecipes and Examples\n------------------\n\nThis section contains a series of contrived examples. Each assume that\nyou've created some functions that *render the tests* for a user.\n\n### Two Parallel 50/50 Tests\n\nYou want to run two tests, \"Big Button\" (shows either a red or blue\nsign up button) and \"New Logo\" (which shows the \"new\" or \"old\"\nlogo). Each test will involve all web site visitors split down the\nmiddle. This gives four possibilities:\n\n![Overlapping Tests Illustration](visuals/overlapping-tests.png)\n\nThe code to run these tests involves two calls to the plugin:\n\n    $.labrats( { name: 'Big Button',\n                 callbacks: [ shinyRed, flashyBlue ] } );\n\n    $.labrats( { name: 'New Logo',\n                 callbacks: [ oldlogo, newlogo ] } );\n\n### Having a Control Group\n\nA *control group* contains the users who see the \"old stuff\". This is\nstill a \"group\". For instance, in the previous example, the \"control\"\nis the group that see the old logo.\n\nHowever, if the logo is particularly daring, and you only wanted to\nshow it to 10% of your users, you would use the `subset` option:\n\n    $.labrats( { name: 'New Logo', subset: 10,\n                 callbacks: [ newlogo ], control: oldlogo } );\n\nNote: The `callbacks` can accept one function (creating a single test\ngroup), but it still only accepts and array.\n\nThe `control` parameter is optional. This could be useful if the old\nlogo is already being displayed and the `newlogo()` function simply\nreplaced it.\n\n### Having a Control plus Test Groups\n\nSuppose your graphics department churned up two hot new company logos\nyou want to A/B test, but you still wanted the bulk of your users to\nsee the old one until after the testing is complete.\n\n    $.labrats( { name: 'New Logo', subset: 50,\n                 callbacks: [ newlogoA, newlogoB ],\n                 control: oldlogo } );\n\nIn the above code, 50% of the users will see the old logo (by calling\nthe `oldlogo()` function). The remaining users are split, so 25% of\nyour users will see the \"A\" logo version (by calling `newlogoA()`) and\n25% will see the \"B\" logo version (by calling `newlogoB()`).\n\nKeep in mind that since we are dealing with random numbers, 25% means\n*about* 25%. For instance, in some of the tests that we ran with 1,000\nuser IDs, we got the following actual distribution:\n\n![Distribution for Test 1](visuals/graph-1.png)\n\n\n### Multiple Non-Overlapping Tests\n\nIn our first recipe, we wanted to test both our sign up button (which\ncould be red or blue) as well as our logo change. What if these tests\nwere so major that you didn't want a customer to see either the red or\nblue buttons if they also saw the new logo?\n\nHere we introduce the concept of a *slice*. Each slice can contain a\nnon-overlapping test... but only from tests in other slices. A slice\n*must* be named. Here is the example code:\n\n    $.labrats( { name: 'Major Tests', slices: 2, slice: 0,\n                 callbacks: [ shinyRed, flashyBlue ] } );\n\n    $.labrats( { name: 'Major Tests', slices: 2, slice: 1,\n                 callbacks: [ oldlogo, newlogo ] } );\n\nIn this code, the `name` refers to the slice collection and the test\nis specified with the `slice` parameter. This divides our users into 4\ngroups of 25% each:\n\n![Discreet Tests Illustration](visuals/discreet-tests.png)\n\n**Note:** Some companies create this \"major\" slicing division with\n  lots of small slices (like 20 slices of 5% each), and then allocate\n  them to tests over time.\n  \n\nFunction API\n------------------\n\nThis section details all available functions in this plugin. While the\nprimary function is `$.labrats()`, fine-grain control may be had with\nthe other functions described below.\n\n### `$.labrats()`\n\nCalls a function based on an assigned test group for a user.\nThe identification key for the user (as well as the name(s) of\nthe test) can be passed in as function arguments along with two\nor more callback functions (note that their order matters).\n\nFor instance, for a test that splits the user accounts into three\ngroups, you could do:\n\n    $.labrats.configure( { groups: 3 } ); // Optional\n    $.labrats(userid, \"Some Test\", fn1, fn2, fn3);\n\nThe other approach to calling this function is with named parameters.\nFor instance, the same example could be written:\n\n    $.labrats({ key: userid, name: \"Some Test\", groups:3,\n                callbacks: [ fn1, fn2, fn3 ] });\n\nThis function returns the results of calling one of the callback\nfunctions.\n\nNote: The size of available pool for tests can be limited (effectively\ncreating a a pool of people in test groups and another control\ngroup).  For instance:\n\n   $.labrats.group( { key: userId, name: \"Another Test\", subset: 10,\n                      callbacks: [ fn1, fn2, fn3 ],\n                      control: fn4 });\n\nWill call the `control` function if the user is part of the 90%\ncontrol group, otherwise, it calls the appropriate function in\nthe `callbacks` array.\n\n(See the `group()` function for details as to the other acceptable values\nfor named parameters)\n\n\n### `$.fn.labrats()`\n\nBehaves like the utility function, `$.labrats()`, but the callback\nfunction is given the jQuery selector results. This allows the callback\nfunction to behave as part of a jQuery chain. For instance:\n\n    function fn1() {\n       return this.addClass(\"shiny-red\");\n    };\n    function fn2() {\n       return this.addClass(\"flashy-blue\");\n    };\n\n    var testCfg = { key: id1, callbacks: [ fn1, fn2 ] };\n    $(\"#logo-test\").labrats(testCfg).click(...);\n\n**Note:** Only *named parameters* work as arguments.\n\n\n### `$.labrats.group()`\n\nDetermines the *group number* assigned to a given user. The number\nof groups can be specified using the `configure()` function (see below).\nThe user's `key` is passed in as the parameter, but this should\nalso take the name of the test as well.\n\nThis can either be specified as parameters, as in:\n\n    $.labrats.group( userID, \"Large Logo Test\" );\n\nOr as a series of keys in an array. The following is equivalent:\n\n    $.labrats.group( [ userID, \"Large Logo Test\" ] );\n\nOr as a collection of named parameters:\n\n    $.labrats.group( { key: userID, name: \"Large Logo Test\",\n                       groups: 2 } );\n\nThis last approach allows you to specify the number of\ngroups (instead of calling the `configure()` function).\n\n#### Accepted Parameters:\n\n - `key` - The identification of the user account\n - `name` - The name of the test. The name is appended to the key in order to compute the hash value. This guarantees that each test has a different distribution of user accounts.\n - `groups` - The number of active groups a non-controlled test account can be in. If not given, this defaults to 0.\n - `subset` - A percentage (from 0 to 100) that Specifies the size of the test pool. User accounts that hash outside this value are part of the control. If not specified, this defaults to `100` (meaning, no control group).\n - `slices` - Divides the test pool into discreet slices where a user account can be in only one slice. This allows distinct test groups that don't overlap essentially guaranteeing that a user account could be in at most, one test group. The slices works for a given `name` parameter.\n - `slice` - The name of the slice this user should belong in order to qualify for being part of a test group.\n - `hash` - The function used to convert the *string* into a number than can be divided into the different test buckets.\n\n#### Limit Test Pool with `subset`\n\nYou can limit the size of available pool (effectively creating a\na pool of people in test groups and another *control group*).  For\ninstance:\n\n    $.labrats.group( { key: userId, name: \"Another Test\",\n                       groups: 2, subset: 10 });\n\nWill return `-1` if the user is part of the 90% control group,\notherwise, it returns either `0` or `1` if it is in one of the\n5% sized test groups.\n\n#### Slicing Test Pool\n\nWith multiple tests, a random distribution algorithm means that some\nusers will end up in more than one test group. The `slices` option\ndivides the test pool into discreet subgroups, and the `slice` option\nspecifies which slice to use for a particular test.\n\nFor instance, suppose you have some experiments that are quite invasive,\n(perhaps even conflicting if a person ended up as a lab rat in more than one),\nwe could define the first experiment to use the first slice:\n\n    $.labrats.group( { key: userId, name: \"serious tests\",\n                       slices: 3, slice: 0, groups: 2 });\n\nThe second experiment would use the next *slice*:\n\n    $.labrats.group( { key: userId, name: \"serious tests\",\n                       slices: 3, slice: 1, groups: 2 });\n\nNotice the test name for the group of slices must be the same.\n\nThis slicing feature can be combined with the `subset` feature to\nkeep a control group out. Also, when using the subset and the slicing\nfeatures, the `groups` option can be unspecified in order to default\nto `1` (a single test group).\n\nWith five experiments where each experiment is in a slice with\ntwo test groups, we might have a distribution illustrated in\nthis diagram:\n\n![Distribution for Test 1](visuals/slice_graph.png)\n\n\n### `$.labrats.inGroup()`\n\nA test to see if a particular 'key' is part of the given group.\nReturns `true` if a given key is in the group number, `false`\notherwise.\n\nThis function can be called either with named parameters, as in:\n\n    $.labrats.inGroup( 2, { key: userid, name: testname }\n\nWhere the first argument is the group number to check, and the\nsecond argument is an object similar to what is passed to the\n`$.labrats.group()` function, including:\n\n  - `key` is the identification of the user\n  - `name` is the test's name\n  - `subset` is the size of the pool, where `100 - subset` is the\n     size of the control group\n\nThis function can also be called as a series of parameters:\n\n    $.labrats.inGroup( 2, userid, testname )\n\n\n### `$.labrats.key()`\n\nConverts a series of arguments into a *key* to use in a\nhash. Each function may call this using a few formats.\nFor instance, as a series of string arguments:\n\n    var key = $.labrats.key(\"test\", \"abc\", id);\n\nor as an array:\n\n    var key = $.labrats.key([id, \"test\", \"abc\"]);\n\nor even as the function's arguments:\n\n    var key = $.labrats.key(arguments);\n\n\n### `$.labrats.getId()`\n\nReturns an unique identification for the current user's browser.\nIf this is the first time a user has seen the application, we\ngenerate a new ID (as a random number), otherwise, we return the\nID stored in a cookie.\n\n\n### `$.labrats.configure()`\n\nThis function allows a single object to overwrite some, but not\nall configuration values. Acceptable values include:\n\n - `hash`: A function used to convert a user ID key and test name into a number\n - `groups`: The number of test groups to divide the user pool\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhowardabrams%2Flabrats","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhowardabrams%2Flabrats","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhowardabrams%2Flabrats/lists"}